KDEUI
kkeysequencewidget.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 1998 Mark Donohoe <donohoe@kde.org> 00003 Copyright (C) 2001 Ellis Whitehead <ellis@kde.org> 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 #include "kkeysequencewidget.h" 00023 #include "kkeysequencewidget_p.h" 00024 00025 #include "kkeyserver.h" 00026 00027 #include <QKeyEvent> 00028 #include <QTimer> 00029 #include <QtCore/QHash> 00030 #include <QHBoxLayout> 00031 #include <QToolButton> 00032 #include <QApplication> 00033 00034 #include <kglobalaccel.h> 00035 #include <kicon.h> 00036 #include <klocale.h> 00037 #include <kmessagebox.h> 00038 #include <kshortcut.h> 00039 #include <kaction.h> 00040 #include <kactioncollection.h> 00041 00042 #include "kdebug.h" 00043 00044 class KKeySequenceWidgetPrivate 00045 { 00046 public: 00047 KKeySequenceWidgetPrivate(KKeySequenceWidget *q); 00048 00049 void init(); 00050 00051 static QKeySequence appendToSequence(const QKeySequence& seq, int keyQt); 00052 static bool isOkWhenModifierless(int keyQt); 00053 00054 void updateShortcutDisplay(); 00055 void startRecording(); 00056 00061 bool conflictWithStandardShortcuts(const QKeySequence &seq); 00062 00067 bool conflictWithLocalShortcuts(const QKeySequence &seq); 00068 00073 bool conflictWithGlobalShortcuts(const QKeySequence &seq); 00074 00078 bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq); 00079 00080 bool checkAgainstStandardShortcuts() const 00081 { 00082 return checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts; 00083 } 00084 00085 bool checkAgainstGlobalShortcuts() const 00086 { 00087 return checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts; 00088 } 00089 00090 bool checkAgainstLocalShortcuts() const 00091 { 00092 return checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts; 00093 } 00094 00095 void controlModifierlessTimout() 00096 { 00097 if (nKey != 0 && !modifierKeys) { 00098 // No modifier key pressed currently. Start the timout 00099 modifierlessTimeout.start(600); 00100 } else { 00101 // A modifier is pressed. Stop the timeout 00102 modifierlessTimeout.stop(); 00103 } 00104 00105 } 00106 00107 00108 void cancelRecording() 00109 { 00110 keySequence = oldKeySequence; 00111 doneRecording(); 00112 } 00113 00114 00115 bool isShiftAsModifierAllowed(int keyQt) 00116 { 00117 // Shift only works as a modifier with certain keys. It's not possible 00118 // to enter the SHIFT+5 key sequence for me because this is handled as 00119 // '%' by qt on my keyboard. 00120 // The working keys are all hardcoded here :-( 00121 if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) 00122 return true; 00123 00124 if (QChar(keyQt).isLetter()) 00125 return true; 00126 00127 switch (keyQt) { 00128 case Qt::Key_Return: 00129 case Qt::Key_Space: 00130 case Qt::Key_Backspace: 00131 case Qt::Key_Escape: 00132 case Qt::Key_Print: 00133 case Qt::Key_ScrollLock: 00134 case Qt::Key_Pause: 00135 case Qt::Key_PageUp: 00136 case Qt::Key_PageDown: 00137 case Qt::Key_Insert: 00138 case Qt::Key_Delete: 00139 case Qt::Key_Home: 00140 case Qt::Key_End: 00141 case Qt::Key_Up: 00142 case Qt::Key_Down: 00143 case Qt::Key_Left: 00144 case Qt::Key_Right: 00145 return true; 00146 00147 default: 00148 return false; 00149 } 00150 } 00151 00152 00153 bool promptStealShortcutSystemwide( 00154 QWidget *parent, 00155 const QHash<QKeySequence, QList<KGlobalShortcutInfo> > &shortcuts, 00156 const QKeySequence &sequence) 00157 { 00158 if (shortcuts.isEmpty()) { 00159 // Usage error. Just say no 00160 return false; 00161 } 00162 00163 QString clashingKeys = ""; 00164 Q_FOREACH (const QKeySequence &seq, shortcuts.keys()) { 00165 Q_FOREACH (const KGlobalShortcutInfo &info, shortcuts[seq]) { 00166 clashingKeys += i18n("Shortcut '%1' in Application %2 for action %3\n", 00167 seq.toString(), 00168 info.componentFriendlyName(), 00169 info.friendlyName()); 00170 } 00171 } 00172 00173 const int hashSize = shortcuts.size(); 00174 00175 QString message = i18ncp("%1 is the number of conflicts (hidden), %2 is the key sequence of the shortcut that is problematic", 00176 "The shortcut '%2' conflicts with the following key combination:\n", 00177 "The shortcut '%2' conflicts with the following key combinations:\n", 00178 hashSize, sequence.toString()); 00179 message+=clashingKeys; 00180 00181 QString title = i18ncp("%1 is the number of shortcuts with which there is a conflict", 00182 "Conflict with Registered Global Shortcut", "Conflict with Registered Global Shortcuts", hashSize); 00183 00184 return KMessageBox::warningContinueCancel(parent, message, title, KGuiItem(i18n("Reassign"))) 00185 == KMessageBox::Continue; 00186 } 00187 00188 00189 //private slot 00190 void doneRecording(bool validate = true); 00191 00192 //members 00193 KKeySequenceWidget *const q; 00194 QHBoxLayout *layout; 00195 KKeySequenceButton *keyButton; 00196 QToolButton *clearButton; 00197 00198 QKeySequence keySequence; 00199 QKeySequence oldKeySequence; 00200 QTimer modifierlessTimeout; 00201 bool allowModifierless; 00202 uint nKey; 00203 uint modifierKeys; 00204 bool isRecording; 00205 bool multiKeyShortcutsAllowed; 00206 QString componentName; 00207 00209 KKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes; 00210 00214 QList<QAction*> checkList; // deprecated 00215 00219 QList<KActionCollection*> checkActionCollections; 00220 00224 QList<KAction*> stealActions; 00225 00226 bool stealShortcuts(const QList<KAction *> &actions, const QKeySequence &seq); 00227 void wontStealShortcut(QAction *item, const QKeySequence &seq); 00228 00229 }; 00230 00231 KKeySequenceWidgetPrivate::KKeySequenceWidgetPrivate(KKeySequenceWidget *q) 00232 : q(q) 00233 ,layout(NULL) 00234 ,keyButton(NULL) 00235 ,clearButton(NULL) 00236 ,allowModifierless(false) 00237 ,nKey(0) 00238 ,modifierKeys(0) 00239 ,isRecording(false) 00240 ,multiKeyShortcutsAllowed(true) 00241 ,componentName() 00242 ,checkAgainstShortcutTypes(KKeySequenceWidget::LocalShortcuts & KKeySequenceWidget::GlobalShortcuts) 00243 ,stealActions() 00244 {} 00245 00246 00247 bool KKeySequenceWidgetPrivate::stealShortcuts( 00248 const QList<KAction *> &actions, 00249 const QKeySequence &seq) 00250 { 00251 00252 const int listSize = actions.size(); 00253 00254 QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize); 00255 00256 QString conflictingShortcuts; 00257 Q_FOREACH(const KAction *action, actions) { 00258 conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n", 00259 action->shortcut().toString(QKeySequence::NativeText), 00260 KGlobal::locale()->removeAcceleratorMarker(action->text())); 00261 } 00262 QString message = i18ncp("%1 is the number of ambigious shortcut clashes (hidden)", 00263 "The \"%2\" shortcut is ambiguous with the following shortcut.\n" 00264 "Do you want to assign an empty shortcut to this action?\n" 00265 "%3", 00266 "The \"%2\" shortcut is ambiguous with the following shortcuts.\n" 00267 "Do you want to assign an empty shortcut to these actions?\n" 00268 "%3", 00269 listSize, 00270 seq.toString(QKeySequence::NativeText), 00271 conflictingShortcuts); 00272 00273 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) 00274 return false; 00275 00276 return true; 00277 } 00278 00279 void KKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq) 00280 { 00281 QString title( i18n( "Shortcut conflict" ) ); 00282 QString msg( i18n( "<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>" 00283 "Please select a different one.</qt>", seq.toString(QKeySequence::NativeText) , 00284 KGlobal::locale()->removeAcceleratorMarker(item->text()) ) ); 00285 KMessageBox::sorry( q, msg ); 00286 } 00287 00288 00289 KKeySequenceWidget::KKeySequenceWidget(QWidget *parent) 00290 : QWidget(parent), 00291 d(new KKeySequenceWidgetPrivate(this)) 00292 { 00293 d->init(); 00294 setFocusProxy( d->keyButton ); 00295 connect(d->keyButton, SIGNAL(clicked()), this, SLOT(captureKeySequence())); 00296 connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearKeySequence())); 00297 connect(&d->modifierlessTimeout, SIGNAL(timeout()), this, SLOT(doneRecording())); 00298 //TODO: how to adopt style changes at runtime? 00299 /*QFont modFont = d->clearButton->font(); 00300 modFont.setStyleHint(QFont::TypeWriter); 00301 d->clearButton->setFont(modFont);*/ 00302 d->updateShortcutDisplay(); 00303 } 00304 00305 00306 void KKeySequenceWidgetPrivate::init() 00307 { 00308 layout = new QHBoxLayout(q); 00309 layout->setMargin(0); 00310 00311 keyButton = new KKeySequenceButton(this, q); 00312 keyButton->setFocusPolicy(Qt::StrongFocus); 00313 keyButton->setIcon(KIcon("configure")); 00314 keyButton->setToolTip(i18n("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+a: hold the Ctrl key and press a.")); 00315 layout->addWidget(keyButton); 00316 00317 clearButton = new QToolButton(q); 00318 layout->addWidget(clearButton); 00319 00320 if (qApp->isLeftToRight()) 00321 clearButton->setIcon(KIcon("edit-clear-locationbar-rtl")); 00322 else 00323 clearButton->setIcon(KIcon("edit-clear-locationbar-ltr")); 00324 } 00325 00326 00327 KKeySequenceWidget::~KKeySequenceWidget () 00328 { 00329 delete d; 00330 } 00331 00332 00333 KKeySequenceWidget::ShortcutTypes KKeySequenceWidget::checkForConflictsAgainst() const 00334 { 00335 return d->checkAgainstShortcutTypes; 00336 } 00337 00338 00339 void KKeySequenceWidget::setComponentName(const QString &componentName) 00340 { 00341 d->componentName = componentName; 00342 } 00343 00344 bool KKeySequenceWidget::multiKeyShortcutsAllowed() const 00345 { 00346 return d->multiKeyShortcutsAllowed; 00347 } 00348 00349 00350 void KKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed) 00351 { 00352 d->multiKeyShortcutsAllowed = allowed; 00353 } 00354 00355 00356 void KKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types) 00357 { 00358 d->checkAgainstShortcutTypes = types; 00359 } 00360 00361 void KKeySequenceWidget::setModifierlessAllowed(bool allow) 00362 { 00363 d->allowModifierless = allow; 00364 } 00365 00366 00367 bool KKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const 00368 { 00369 if (keySequence.isEmpty()) 00370 return true; 00371 return ! ( d->conflictWithLocalShortcuts(keySequence) 00372 || d->conflictWithGlobalShortcuts(keySequence) 00373 || d->conflictWithStandardShortcuts(keySequence)); 00374 } 00375 00376 00377 bool KKeySequenceWidget::isModifierlessAllowed() 00378 { 00379 return d->allowModifierless; 00380 } 00381 00382 00383 void KKeySequenceWidget::setClearButtonShown(bool show) 00384 { 00385 d->clearButton->setVisible(show); 00386 } 00387 00388 #ifndef KDE_NO_DEPRECATED 00389 void KKeySequenceWidget::setCheckActionList(const QList<QAction*> &checkList) // deprecated 00390 { 00391 d->checkList = checkList; 00392 Q_ASSERT(d->checkActionCollections.isEmpty()); // don't call this method if you call setCheckActionCollections! 00393 } 00394 #endif 00395 00396 void KKeySequenceWidget::setCheckActionCollections(const QList<KActionCollection *>& actionCollections) 00397 { 00398 d->checkActionCollections = actionCollections; 00399 } 00400 00401 //slot 00402 void KKeySequenceWidget::captureKeySequence() 00403 { 00404 d->startRecording(); 00405 } 00406 00407 00408 QKeySequence KKeySequenceWidget::keySequence() const 00409 { 00410 return d->keySequence; 00411 } 00412 00413 00414 //slot 00415 void KKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate) 00416 { 00417 // oldKeySequence holds the key sequence before recording started, if setKeySequence() 00418 // is called while not recording then set oldKeySequence to the existing sequence so 00419 // that the keySequenceChanged() signal is emitted if the new and previous key 00420 // sequences are different 00421 if (!d->isRecording) 00422 d->oldKeySequence = d->keySequence; 00423 00424 d->keySequence = seq; 00425 d->doneRecording(validate == Validate); 00426 } 00427 00428 00429 //slot 00430 void KKeySequenceWidget::clearKeySequence() 00431 { 00432 setKeySequence(QKeySequence()); 00433 } 00434 00435 //slot 00436 void KKeySequenceWidget::applyStealShortcut() 00437 { 00438 QSet<KActionCollection *> changedCollections; 00439 00440 Q_FOREACH (KAction *stealAction, d->stealActions) { 00441 00442 // Stealing a shortcut means setting it to an empty one. 00443 stealAction->setShortcut(KShortcut(), KAction::ActiveShortcut); 00444 00445 // The following code will find the action we are about to 00446 // steal from and save it's actioncollection. 00447 KActionCollection* parentCollection = 0; 00448 foreach(KActionCollection* collection, d->checkActionCollections) { 00449 if (collection->actions().contains(stealAction)) { 00450 parentCollection = collection; 00451 break; 00452 } 00453 } 00454 00455 // Remember the changed collection 00456 if (parentCollection) { 00457 changedCollections.insert(parentCollection); 00458 } 00459 } 00460 00461 Q_FOREACH (KActionCollection *col, changedCollections) { 00462 col->writeSettings(); 00463 } 00464 00465 d->stealActions.clear(); 00466 } 00467 00468 void KKeySequenceButton::setText(const QString &text) 00469 { 00470 QPushButton::setText(text); 00471 //setFixedSize( sizeHint().width()+12, sizeHint().height()+8 ); 00472 } 00473 00474 00475 void KKeySequenceWidgetPrivate::startRecording() 00476 { 00477 nKey = 0; 00478 modifierKeys = 0; 00479 oldKeySequence = keySequence; 00480 keySequence = QKeySequence(); 00481 isRecording = true; 00482 keyButton->grabKeyboard(); 00483 00484 if (!QWidget::keyboardGrabber()) { 00485 kWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active"; 00486 } 00487 00488 keyButton->setDown(true); 00489 updateShortcutDisplay(); 00490 } 00491 00492 00493 void KKeySequenceWidgetPrivate::doneRecording(bool validate) 00494 { 00495 modifierlessTimeout.stop(); 00496 isRecording = false; 00497 keyButton->releaseKeyboard(); 00498 keyButton->setDown(false); 00499 stealActions.clear(); 00500 00501 if (keySequence==oldKeySequence) { 00502 // The sequence hasn't changed 00503 updateShortcutDisplay(); 00504 return; 00505 } 00506 00507 if (validate && !q->isKeySequenceAvailable(keySequence)) { 00508 // The sequence had conflicts and the user said no to stealing it 00509 keySequence = oldKeySequence; 00510 } else { 00511 emit q->keySequenceChanged(keySequence); 00512 } 00513 00514 updateShortcutDisplay(); 00515 } 00516 00517 00518 bool KKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence) 00519 { 00520 #ifdef Q_WS_WIN 00521 //on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut 00522 if (KKeySequenceWidget::GlobalShortcuts && keySequence.toString().contains("F12")) { 00523 QString title = i18n("Reserved Shortcut"); 00524 QString message = i18n("The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n" 00525 "Please choose another one."); 00526 00527 KMessageBox::sorry(q, message, title); 00528 return false; 00529 } 00530 #endif 00531 00532 if (!(checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts)) { 00533 return false; 00534 } 00535 00536 // Global shortcuts are on key+modifier shortcuts. They can clash with 00537 // each of the keys of a multi key shortcut. 00538 QHash<QKeySequence, QList<KGlobalShortcutInfo> > others; 00539 for (uint i=0; i<keySequence.count(); ++i) { 00540 QKeySequence tmp(keySequence[i]); 00541 00542 if (!KGlobalAccel::isGlobalShortcutAvailable(tmp, componentName)) { 00543 others.insert(tmp, KGlobalAccel::getGlobalShortcutsByKey(tmp)); 00544 } 00545 } 00546 00547 if (!others.isEmpty() 00548 && !promptStealShortcutSystemwide(q, others, keySequence)) { 00549 return true; 00550 } 00551 00552 // The user approved stealing the shortcut. We have to steal 00553 // it immediately because KAction::setGlobalShortcut() refuses 00554 // to set a global shortcut that is already used. There is no 00555 // error it just silently fails. So be nice because this is 00556 // most likely the first action that is done in the slot 00557 // listening to keySequenceChanged(). 00558 for (uint i=0; i<keySequence.count(); ++i) { 00559 KGlobalAccel::stealShortcutSystemwide(keySequence[i]); 00560 } 00561 return false; 00562 } 00563 00564 00565 bool KKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence) 00566 { 00567 if (!(checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts)) { 00568 return false; 00569 } 00570 00571 // We have actions both in the deprecated checkList and the 00572 // checkActionCollections list. Add all the actions to a single list to 00573 // be able to process them in a single loop below. 00574 // Note that this can't be done in setCheckActionCollections(), because we 00575 // keep pointers to the action collections, and between the call to 00576 // setCheckActionCollections() and this function some actions might already be 00577 // removed from the collection again. 00578 QList<QAction*> allActions; 00579 allActions += checkList; 00580 foreach(KActionCollection* collection, checkActionCollections) { 00581 allActions += collection->actions(); 00582 } 00583 00584 // Because of multikey shortcuts we can have clashes with many shortcuts. 00585 // 00586 // Example 1: 00587 // 00588 // Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F' 00589 // and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as 00590 // 'activatedAmbiguously()' for obvious reasons. 00591 // 00592 // Example 2: 00593 // 00594 // Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'. 00595 // This will shadow 'CTRL-X' for the same reason as above. 00596 // 00597 // Example 3: 00598 // 00599 // Some weird combination of Example 1 and 2 with three shortcuts using 00600 // 1/2/3 key shortcuts. I think you can imagine. 00601 QList<KAction*> conflictingActions; 00602 00603 //find conflicting shortcuts with existing actions 00604 foreach(QAction * qaction , allActions ) { 00605 KAction *kaction=qobject_cast<KAction*>(qaction); 00606 if(kaction) { 00607 if(kaction->shortcut().conflictsWith(keySequence)) { 00608 // A conflict with a KAction. If that action is configurable 00609 // ask the user what to do. If not reject this keySequence. 00610 if(kaction->isShortcutConfigurable ()) { 00611 conflictingActions.append(kaction); 00612 } else { 00613 wontStealShortcut(kaction, keySequence); 00614 return true; 00615 } 00616 } 00617 } else { 00618 if(qaction->shortcut() == keySequence) { 00619 // A conflict with a QAction. Don't know why :-( but we won't 00620 // steal from that kind of actions. 00621 wontStealShortcut(qaction, keySequence); 00622 return true; 00623 } 00624 } 00625 } 00626 00627 if (conflictingActions.isEmpty()) { 00628 // No conflicting shortcuts found. 00629 return false; 00630 } 00631 00632 if(stealShortcuts(conflictingActions, keySequence)) { 00633 stealActions = conflictingActions; 00634 // Announce that the user 00635 // agreed 00636 Q_FOREACH (KAction *stealAction, stealActions) { 00637 emit q->stealShortcut( 00638 keySequence, 00639 stealAction); 00640 } 00641 return false; 00642 } else { 00643 return true; 00644 } 00645 } 00646 00647 00648 bool KKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence) 00649 { 00650 if (!(checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts)) { 00651 return false; 00652 } 00653 00654 KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence); 00655 if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) { 00656 return true; 00657 } 00658 return false; 00659 } 00660 00661 00662 bool KKeySequenceWidgetPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq) 00663 { 00664 QString title = i18n("Conflict with Standard Application Shortcut"); 00665 QString message = i18n("The '%1' key combination is also used for the standard action " 00666 "\"%2\" that some applications use.\n" 00667 "Do you really want to use it as a global shortcut as well?", 00668 seq.toString(QKeySequence::NativeText), KStandardShortcut::label(std)); 00669 00670 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { 00671 return false; 00672 } 00673 return true; 00674 } 00675 00676 00677 void KKeySequenceWidgetPrivate::updateShortcutDisplay() 00678 { 00679 //empty string if no non-modifier was pressed 00680 QString s = keySequence.toString(QKeySequence::NativeText); 00681 s.replace('&', QLatin1String("&&")); 00682 00683 if (isRecording) { 00684 if (modifierKeys) { 00685 if (!s.isEmpty()) s.append(","); 00686 if (modifierKeys & Qt::META) s += KKeyServer::modToStringUser(Qt::META) + '+'; 00687 #if defined(Q_WS_MAC) 00688 if (modifierKeys & Qt::ALT) s += KKeyServer::modToStringUser(Qt::ALT) + '+'; 00689 if (modifierKeys & Qt::CTRL) s += KKeyServer::modToStringUser(Qt::CTRL) + '+'; 00690 #elif defined(Q_WS_X11) 00691 if (modifierKeys & Qt::CTRL) s += KKeyServer::modToStringUser(Qt::CTRL) + '+'; 00692 if (modifierKeys & Qt::ALT) s += KKeyServer::modToStringUser(Qt::ALT) + '+'; 00693 #endif 00694 if (modifierKeys & Qt::SHIFT) s += KKeyServer::modToStringUser(Qt::SHIFT) + '+'; 00695 00696 } else if (nKey == 0) { 00697 s = i18nc("What the user inputs now will be taken as the new shortcut", "Input"); 00698 } 00699 //make it clear that input is still going on 00700 s.append(" ..."); 00701 } 00702 00703 if (s.isEmpty()) { 00704 s = i18nc("No shortcut defined", "None"); 00705 } 00706 00707 s.prepend(' '); 00708 s.append(' '); 00709 keyButton->setText(s); 00710 } 00711 00712 00713 KKeySequenceButton::~KKeySequenceButton() 00714 { 00715 } 00716 00717 00718 //prevent Qt from special casing Tab and Backtab 00719 bool KKeySequenceButton::event (QEvent* e) 00720 { 00721 if (d->isRecording && e->type() == QEvent::KeyPress) { 00722 keyPressEvent(static_cast<QKeyEvent *>(e)); 00723 return true; 00724 } 00725 00726 // The shortcut 'alt+c' ( or any other dialog local action shortcut ) 00727 // ended the recording and triggered the action associated with the 00728 // action. In case of 'alt+c' ending the dialog. It seems that those 00729 // ShortcutOverride events get sent even if grabKeyboard() is active. 00730 if (d->isRecording && e->type() == QEvent::ShortcutOverride) { 00731 e->accept(); 00732 return true; 00733 } 00734 00735 return QPushButton::event(e); 00736 } 00737 00738 00739 void KKeySequenceButton::keyPressEvent(QKeyEvent *e) 00740 { 00741 int keyQt = e->key(); 00742 if (keyQt == -1) { 00743 // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key. 00744 // We cannot do anything useful with those (several keys have -1, indistinguishable) 00745 // and QKeySequence.toString() will also yield a garbage string. 00746 KMessageBox::sorry(this, 00747 i18n("The key you just pressed is not supported by Qt."), 00748 i18n("Unsupported Key")); 00749 return d->cancelRecording(); 00750 } 00751 00752 uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); 00753 00754 //don't have the return or space key appear as first key of the sequence when they 00755 //were pressed to start editing - catch and them and imitate their effect 00756 if (!d->isRecording && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) { 00757 d->startRecording(); 00758 d->modifierKeys = newModifiers; 00759 d->updateShortcutDisplay(); 00760 return; 00761 } 00762 00763 // We get events even if recording isn't active. 00764 if (!d->isRecording) 00765 return QPushButton::keyPressEvent(e); 00766 00767 e->accept(); 00768 d->modifierKeys = newModifiers; 00769 00770 00771 switch(keyQt) { 00772 case Qt::Key_AltGr: //or else we get unicode salad 00773 return; 00774 case Qt::Key_Shift: 00775 case Qt::Key_Control: 00776 case Qt::Key_Alt: 00777 case Qt::Key_Meta: 00778 case Qt::Key_Menu: //unused (yes, but why?) 00779 d->controlModifierlessTimout(); 00780 d->updateShortcutDisplay(); 00781 break; 00782 default: 00783 00784 if (d->nKey == 0 && !(d->modifierKeys & ~Qt::SHIFT)) { 00785 // It's the first key and no modifier pressed. Check if this is 00786 // allowed 00787 if (!(KKeySequenceWidgetPrivate::isOkWhenModifierless(keyQt) 00788 || d->allowModifierless)) { 00789 // No it's not 00790 return; 00791 } 00792 } 00793 00794 // We now have a valid key press. 00795 if (keyQt) { 00796 if ((keyQt == Qt::Key_Backtab) && (d->modifierKeys & Qt::SHIFT)) { 00797 keyQt = Qt::Key_Tab | d->modifierKeys; 00798 } 00799 else if (d->isShiftAsModifierAllowed(keyQt)) { 00800 keyQt |= d->modifierKeys; 00801 } 00802 else 00803 keyQt |= (d->modifierKeys & ~Qt::SHIFT); 00804 00805 if (d->nKey == 0) { 00806 d->keySequence = QKeySequence(keyQt); 00807 } else { 00808 d->keySequence = 00809 KKeySequenceWidgetPrivate::appendToSequence(d->keySequence, keyQt); 00810 } 00811 00812 d->nKey++; 00813 if ((!d->multiKeyShortcutsAllowed) || (d->nKey >= 4)) { 00814 d->doneRecording(); 00815 return; 00816 } 00817 d->controlModifierlessTimout(); 00818 d->updateShortcutDisplay(); 00819 } 00820 } 00821 } 00822 00823 00824 void KKeySequenceButton::keyReleaseEvent(QKeyEvent *e) 00825 { 00826 if (e->key() == -1) { 00827 // ignore garbage, see keyPressEvent() 00828 return; 00829 } 00830 00831 if (!d->isRecording) 00832 return QPushButton::keyReleaseEvent(e); 00833 00834 e->accept(); 00835 00836 uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); 00837 00838 //if a modifier that belongs to the shortcut was released... 00839 if ((newModifiers & d->modifierKeys) < d->modifierKeys) { 00840 d->modifierKeys = newModifiers; 00841 d->controlModifierlessTimout(); 00842 d->updateShortcutDisplay(); 00843 } 00844 } 00845 00846 00847 //static 00848 QKeySequence KKeySequenceWidgetPrivate::appendToSequence(const QKeySequence& seq, int keyQt) 00849 { 00850 switch (seq.count()) { 00851 case 0: 00852 return QKeySequence(keyQt); 00853 case 1: 00854 return QKeySequence(seq[0], keyQt); 00855 case 2: 00856 return QKeySequence(seq[0], seq[1], keyQt); 00857 case 3: 00858 return QKeySequence(seq[0], seq[1], seq[2], keyQt); 00859 default: 00860 return seq; 00861 } 00862 } 00863 00864 00865 //static 00866 bool KKeySequenceWidgetPrivate::isOkWhenModifierless(int keyQt) 00867 { 00868 //this whole function is a hack, but especially the first line of code 00869 if (QKeySequence(keyQt).toString().length() == 1) 00870 return false; 00871 00872 switch (keyQt) { 00873 case Qt::Key_Return: 00874 case Qt::Key_Space: 00875 case Qt::Key_Tab: 00876 case Qt::Key_Backtab: //does this ever happen? 00877 case Qt::Key_Backspace: 00878 case Qt::Key_Delete: 00879 return false; 00880 default: 00881 return true; 00882 } 00883 } 00884 00885 #include "kkeysequencewidget.moc" 00886 #include "kkeysequencewidget_p.moc"
KDE 4.6 API Reference