File indexing completed on 2024-12-22 04:14:07
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> 0003 SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> 0004 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "kkeysequencewidget.h" 0010 #include "kkeysequencewidget_p.h" 0011 #include "config-xmlgui.h" 0012 #include <QAction> 0013 #include <QKeyEvent> 0014 #include <QTimer> 0015 #include <QHash> 0016 #include <QHBoxLayout> 0017 #include <QToolButton> 0018 #include <QApplication> 0019 #include <QDebug> 0020 0021 #include <klocalizedstring.h> 0022 #include <kmessagebox.h> 0023 #include <kkeyserver.h> 0024 #include "kactioncollection.h" 0025 0026 #include <kis_icon_utils.h> 0027 0028 #include <QtGui/private/qkeymapper_p.h> 0029 0030 0031 uint qHash(const QKeySequence &seq) 0032 { 0033 return qHash(seq.toString()); 0034 } 0035 0036 class KisKKeySequenceWidgetPrivate 0037 { 0038 public: 0039 KisKKeySequenceWidgetPrivate(KisKKeySequenceWidget *q); 0040 0041 void init(); 0042 0043 static QKeySequence appendToSequence(const QKeySequence &seq, int keyQt); 0044 static bool isOkWhenModifierless(int keyQt); 0045 0046 void updateShortcutDisplay(); 0047 void startRecording(); 0048 0049 /** 0050 * Conflicts the key sequence @a seq with a current standard 0051 * shortcut? 0052 * 0053 * Pops up a dialog asking overriding the conflict is OK. 0054 */ 0055 bool conflictWithStandardShortcuts(const QKeySequence &seq); 0056 0057 /** 0058 * Conflicts the key sequence @a seq with a current local 0059 * shortcut? 0060 */ 0061 bool conflictWithLocalShortcuts(const QKeySequence &seq); 0062 0063 /** 0064 * Conflicts the key sequence @a seq conflict with Windows shortcut keys? 0065 */ 0066 bool conflictWithGlobalShortcuts(const QKeySequence &seq); 0067 0068 /** 0069 * Get permission to steal the shortcut @seq from the standard shortcut @a std. 0070 */ 0071 bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq); 0072 0073 bool checkAgainstStandardShortcuts() const 0074 { 0075 return checkAgainstShortcutTypes & KisKKeySequenceWidget::StandardShortcuts; 0076 } 0077 0078 bool checkAgainstGlobalShortcuts() const 0079 { 0080 return checkAgainstShortcutTypes & KisKKeySequenceWidget::GlobalShortcuts; 0081 } 0082 0083 bool checkAgainstLocalShortcuts() const 0084 { 0085 return checkAgainstShortcutTypes & KisKKeySequenceWidget::LocalShortcuts; 0086 } 0087 0088 void controlModifierlessTimeout() 0089 { 0090 if (nKey != 0 && !modifierKeys) { 0091 // No modifier key pressed currently. Start the timeout 0092 modifierlessTimeout.start(600); 0093 } else { 0094 // A modifier is pressed. Stop the timeout 0095 modifierlessTimeout.stop(); 0096 } 0097 0098 } 0099 0100 void cancelRecording() 0101 { 0102 keySequence = oldKeySequence; 0103 doneRecording(); 0104 } 0105 0106 //private slot 0107 void doneRecording(bool validate = true); 0108 0109 //members 0110 KisKKeySequenceWidget *const q; 0111 QHBoxLayout *layout; 0112 KKeySequenceButton *keyButton; 0113 QToolButton *clearButton; 0114 0115 QKeySequence keySequence; 0116 QKeySequence oldKeySequence; 0117 QTimer modifierlessTimeout; 0118 bool allowModifierless; 0119 uint nKey; 0120 uint modifierKeys; 0121 bool isRecording; 0122 bool multiKeyShortcutsAllowed; 0123 QString componentName; 0124 0125 //! Check the key sequence against KStandardShortcut::find() 0126 KisKKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes; 0127 0128 /** 0129 * The list of action to check against for conflict shortcut 0130 */ 0131 QList<QAction *> checkList; // deprecated 0132 0133 /** 0134 * The list of action collections to check against for conflict shortcut 0135 */ 0136 QList<KisKActionCollection *> checkActionCollections; 0137 0138 /** 0139 * The action to steal the shortcut from. 0140 */ 0141 QList<QAction *> stealActions; 0142 0143 bool stealShortcuts(const QList<QAction *> &actions, const QKeySequence &seq); 0144 void wontStealShortcut(QAction *item, const QKeySequence &seq); 0145 0146 }; 0147 0148 KisKKeySequenceWidgetPrivate::KisKKeySequenceWidgetPrivate(KisKKeySequenceWidget *q) 0149 : q(q) 0150 , layout(0) 0151 , keyButton(0) 0152 , clearButton(0) 0153 , allowModifierless(false) 0154 , nKey(0) 0155 , modifierKeys(0) 0156 , isRecording(false) 0157 , multiKeyShortcutsAllowed(true) 0158 , componentName() 0159 , checkAgainstShortcutTypes(KisKKeySequenceWidget::LocalShortcuts | KisKKeySequenceWidget::GlobalShortcuts) 0160 , stealActions() 0161 {} 0162 0163 bool KisKKeySequenceWidgetPrivate::stealShortcuts( 0164 const QList<QAction *> &actions, 0165 const QKeySequence &seq) 0166 { 0167 0168 const int listSize = actions.size(); 0169 0170 QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize); 0171 0172 QString conflictingShortcuts; 0173 Q_FOREACH (const QAction *action, actions) { 0174 conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n", 0175 action->shortcut().toString(QKeySequence::NativeText), 0176 KLocalizedString::removeAcceleratorMarker(action->text())); 0177 } 0178 QString message = i18ncp("%1 is the number of ambiguous shortcut clashes (hidden)", 0179 "The \"%2\" shortcut is ambiguous with the following shortcut.\n" 0180 "Do you want to assign an empty shortcut to this action?\n" 0181 "%3", 0182 "The \"%2\" shortcut is ambiguous with the following shortcuts.\n" 0183 "Do you want to assign an empty shortcut to these actions?\n" 0184 "%3", 0185 listSize, 0186 seq.toString(QKeySequence::NativeText), 0187 conflictingShortcuts); 0188 0189 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { 0190 return false; 0191 } 0192 0193 return true; 0194 } 0195 0196 void KisKKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq) 0197 { 0198 QString title(i18n("Shortcut conflict")); 0199 QString msg(i18n("<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>" 0200 "Please select a different one.</qt>", seq.toString(QKeySequence::NativeText), 0201 KLocalizedString::removeAcceleratorMarker(item->text()))); 0202 KMessageBox::sorry(q, msg, title); 0203 } 0204 0205 KisKKeySequenceWidget::KisKKeySequenceWidget(QWidget *parent) 0206 : QWidget(parent), 0207 d(new KisKKeySequenceWidgetPrivate(this)) 0208 { 0209 d->init(); 0210 setFocusProxy(d->keyButton); 0211 connect(d->keyButton, SIGNAL(clicked()), this, SLOT(captureKeySequence())); 0212 connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearKeySequence())); 0213 connect(&d->modifierlessTimeout, SIGNAL(timeout()), this, SLOT(doneRecording())); 0214 //TODO: how to adopt style changes at runtime? 0215 /*QFont modFont = d->clearButton->font(); 0216 modFont.setStyleHint(QFont::TypeWriter); 0217 d->clearButton->setFont(modFont);*/ 0218 d->updateShortcutDisplay(); 0219 } 0220 0221 void KisKKeySequenceWidgetPrivate::init() 0222 { 0223 layout = new QHBoxLayout(q); 0224 layout->setMargin(0); 0225 0226 keyButton = new KKeySequenceButton(this, q); 0227 keyButton->setFocusPolicy(Qt::StrongFocus); 0228 keyButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("configure"))); 0229 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.")); 0230 layout->addWidget(keyButton); 0231 0232 clearButton = new QToolButton(q); 0233 layout->addWidget(clearButton); 0234 0235 if (qApp->isLeftToRight()) { 0236 clearButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("edit-clear-locationbar-rtl"))); 0237 } else { 0238 clearButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("edit-clear-locationbar-ltr"))); 0239 } 0240 } 0241 0242 KisKKeySequenceWidget::~KisKKeySequenceWidget() 0243 { 0244 delete d; 0245 } 0246 0247 KisKKeySequenceWidget::ShortcutTypes KisKKeySequenceWidget::checkForConflictsAgainst() const 0248 { 0249 return d->checkAgainstShortcutTypes; 0250 } 0251 0252 void KisKKeySequenceWidget::setComponentName(const QString &componentName) 0253 { 0254 d->componentName = componentName; 0255 } 0256 0257 bool KisKKeySequenceWidget::multiKeyShortcutsAllowed() const 0258 { 0259 return d->multiKeyShortcutsAllowed; 0260 } 0261 0262 void KisKKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed) 0263 { 0264 d->multiKeyShortcutsAllowed = allowed; 0265 } 0266 0267 void KisKKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types) 0268 { 0269 d->checkAgainstShortcutTypes = types; 0270 } 0271 0272 void KisKKeySequenceWidget::setModifierlessAllowed(bool allow) 0273 { 0274 d->allowModifierless = allow; 0275 } 0276 0277 bool KisKKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const 0278 { 0279 if (keySequence.isEmpty()) { 0280 // qDebug() << "Key sequence" << keySequence.toString() << "is empty and available."; 0281 return true; 0282 } 0283 0284 bool hasConflict = (d->conflictWithLocalShortcuts(keySequence) 0285 || d->conflictWithGlobalShortcuts(keySequence) 0286 || d->conflictWithStandardShortcuts(keySequence)); 0287 0288 if (hasConflict) { 0289 /* qInfo() << "Key sequence" << keySequence.toString() << "has an unresolvable conflict." << 0290 QString("Local conflict: %1. Windows conflict: %2. Standard Shortcut conflict: %3") \ 0291 .arg(d->conflictWithLocalShortcuts(keySequence)) \ 0292 .arg(d->conflictWithGlobalShortcuts(keySequence)) \ 0293 .arg(d->conflictWithStandardShortcuts(keySequence)); */ 0294 } 0295 return !(hasConflict); 0296 0297 } 0298 0299 bool KisKKeySequenceWidget::isModifierlessAllowed() 0300 { 0301 return d->allowModifierless; 0302 } 0303 0304 void KisKKeySequenceWidget::setClearButtonShown(bool show) 0305 { 0306 d->clearButton->setVisible(show); 0307 } 0308 0309 void KisKKeySequenceWidget::setCheckActionCollections(const QList<KisKActionCollection *> &actionCollections) 0310 { 0311 d->checkActionCollections = actionCollections; 0312 } 0313 0314 //slot 0315 void KisKKeySequenceWidget::captureKeySequence() 0316 { 0317 d->startRecording(); 0318 } 0319 0320 QKeySequence KisKKeySequenceWidget::keySequence() const 0321 { 0322 return d->keySequence; 0323 } 0324 0325 //slot 0326 void KisKKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate) 0327 { 0328 // oldKeySequence holds the key sequence before recording started, if setKeySequence() 0329 // is called while not recording then set oldKeySequence to the existing sequence so 0330 // that the keySequenceChanged() signal is emitted if the new and previous key 0331 // sequences are different 0332 if (!d->isRecording) { 0333 d->oldKeySequence = d->keySequence; 0334 } 0335 0336 d->keySequence = seq; 0337 d->doneRecording(validate == Validate); 0338 } 0339 0340 //slot 0341 void KisKKeySequenceWidget::clearKeySequence() 0342 { 0343 setKeySequence(QKeySequence()); 0344 } 0345 0346 //slot 0347 void KisKKeySequenceWidget::applyStealShortcut() 0348 { 0349 QSet<KisKActionCollection *> changedCollections; 0350 0351 Q_FOREACH (QAction *stealAction, d->stealActions) { 0352 0353 // Stealing a shortcut means setting it to an empty one. 0354 stealAction->setShortcuts(QList<QKeySequence>()); 0355 0356 // The following code will find the action we are about to 0357 // steal from and save its action collection. 0358 KisKActionCollection *parentCollection = 0; 0359 foreach (KisKActionCollection *collection, d->checkActionCollections) { 0360 if (collection->actions().contains(stealAction)) { 0361 parentCollection = collection; 0362 break; 0363 } 0364 } 0365 0366 // Remember the changed collection 0367 if (parentCollection) { 0368 changedCollections.insert(parentCollection); 0369 } 0370 } 0371 0372 Q_FOREACH (KisKActionCollection *col, changedCollections) { 0373 col->writeSettings(); 0374 } 0375 0376 d->stealActions.clear(); 0377 } 0378 0379 void KisKKeySequenceWidgetPrivate::startRecording() 0380 { 0381 nKey = 0; 0382 modifierKeys = 0; 0383 oldKeySequence = keySequence; 0384 keySequence = QKeySequence(); 0385 isRecording = true; 0386 keyButton->grabKeyboard(); 0387 0388 if (!QWidget::keyboardGrabber()) { 0389 qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active"; 0390 } 0391 0392 keyButton->setDown(true); 0393 updateShortcutDisplay(); 0394 } 0395 0396 void KisKKeySequenceWidgetPrivate::doneRecording(bool validate) 0397 { 0398 modifierlessTimeout.stop(); 0399 isRecording = false; 0400 keyButton->releaseKeyboard(); 0401 keyButton->setDown(false); 0402 stealActions.clear(); 0403 0404 if (keySequence == oldKeySequence) { 0405 // The sequence hasn't changed 0406 updateShortcutDisplay(); 0407 return; 0408 } 0409 0410 if (validate && !q->isKeySequenceAvailable(keySequence)) { 0411 // The sequence had conflicts and the user said no to stealing it 0412 keySequence = oldKeySequence; 0413 } else { 0414 emit q->keySequenceChanged(keySequence); 0415 } 0416 0417 updateShortcutDisplay(); 0418 } 0419 0420 bool KisKKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence) 0421 { 0422 // This could hold some OS-specific stuff, or it could be linked back with 0423 // the KDE global shortcut code at some point in the future. 0424 0425 #ifdef Q_OS_WIN 0426 #else 0427 #endif 0428 Q_UNUSED(keySequence); 0429 0430 return false; 0431 } 0432 0433 bool shortcutsConflictWith(const QList<QKeySequence> &shortcuts, const QKeySequence &needle) 0434 { 0435 if (needle.isEmpty() || needle.toString(QKeySequence::NativeText).isEmpty()) { 0436 return false; 0437 } 0438 0439 foreach (const QKeySequence &sequence, shortcuts) { 0440 if (sequence.isEmpty()) { 0441 continue; 0442 } 0443 0444 if (sequence.matches(needle) != QKeySequence::NoMatch 0445 || needle.matches(sequence) != QKeySequence::NoMatch) { 0446 return true; 0447 } 0448 } 0449 0450 return false; 0451 } 0452 0453 bool KisKKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence) 0454 { 0455 if (!(checkAgainstShortcutTypes & KisKKeySequenceWidget::LocalShortcuts)) { 0456 return false; 0457 } 0458 0459 // We have actions both in the deprecated checkList and the 0460 // checkActionCollections list. Add all the actions to a single list to 0461 // be able to process them in a single loop below. 0462 // Note that this can't be done in setCheckActionCollections(), because we 0463 // keep pointers to the action collections, and between the call to 0464 // setCheckActionCollections() and this function some actions might already be 0465 // removed from the collection again. 0466 QList<QAction *> allActions; 0467 allActions += checkList; 0468 foreach (KisKActionCollection *collection, checkActionCollections) { 0469 allActions += collection->actions(); 0470 } 0471 0472 // Because of multikey shortcuts we can have clashes with many shortcuts. 0473 // 0474 // Example 1: 0475 // 0476 // Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F' 0477 // and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as 0478 // 'activatedAmbiguously()' for obvious reasons. 0479 // 0480 // Example 2: 0481 // 0482 // Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'. 0483 // This will shadow 'CTRL-X' for the same reason as above. 0484 // 0485 // Example 3: 0486 // 0487 // Some weird combination of Example 1 and 2 with three shortcuts using 0488 // 1/2/3 key shortcuts. I think you can imagine. 0489 QList<QAction *> conflictingActions; 0490 0491 //find conflicting shortcuts with existing actions 0492 foreach (QAction *qaction, allActions) { 0493 if (shortcutsConflictWith(qaction->shortcuts(), keySequence)) { 0494 // A conflict with a KAction. If that action is configurable 0495 // ask the user what to do. If not reject this keySequence. 0496 if (checkActionCollections.first()->isShortcutsConfigurable(qaction)) { 0497 conflictingActions.append(qaction); 0498 } else { 0499 wontStealShortcut(qaction, keySequence); 0500 return true; 0501 } 0502 } 0503 } 0504 0505 if (conflictingActions.isEmpty()) { 0506 // No conflicting shortcuts found. 0507 return false; 0508 } 0509 0510 if (stealShortcuts(conflictingActions, keySequence)) { 0511 stealActions = conflictingActions; 0512 0513 // Announce that the user agreed to override the other shortcut 0514 Q_FOREACH (QAction *stealAction, stealActions) { 0515 emit q->stealShortcut( 0516 keySequence, 0517 stealAction); 0518 } 0519 return false; 0520 } else { 0521 return true; 0522 } 0523 } 0524 0525 bool KisKKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence) 0526 { 0527 if (!(checkAgainstShortcutTypes & KisKKeySequenceWidget::StandardShortcuts)) { 0528 return false; 0529 } 0530 KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence); 0531 if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) { 0532 return true; 0533 } 0534 return false; 0535 } 0536 0537 bool KisKKeySequenceWidgetPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq) 0538 { 0539 QString title = i18n("Conflict with Standard Application Shortcut"); 0540 QString message = i18n("The '%1' key combination is also used for the standard action " 0541 "\"%2\" that some applications use.\n" 0542 "Do you really want to use it as a global shortcut as well?", 0543 seq.toString(QKeySequence::NativeText), KStandardShortcut::label(std)); 0544 0545 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { 0546 return false; 0547 } 0548 return true; 0549 } 0550 0551 void KisKKeySequenceWidgetPrivate::updateShortcutDisplay() 0552 { 0553 //empty string if no non-modifier was pressed 0554 QString s = keySequence.toString(QKeySequence::NativeText); 0555 s.replace(QLatin1Char('&'), QStringLiteral("&&")); 0556 0557 if (isRecording) { 0558 if (modifierKeys) { 0559 if (!s.isEmpty()) { 0560 s.append(QLatin1Char(',')); 0561 } 0562 if (modifierKeys & Qt::MetaModifier) { 0563 s += QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText); 0564 } 0565 #if defined(Q_OS_MAC) 0566 if (modifierKeys & Qt::AltModifier) { 0567 s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText); 0568 } 0569 if (modifierKeys & Qt::ControlModifier) { 0570 s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText); 0571 } 0572 #else 0573 if (modifierKeys & Qt::ControlModifier) { 0574 s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText); 0575 } 0576 if (modifierKeys & Qt::AltModifier) { 0577 s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText); 0578 } 0579 #endif 0580 if (modifierKeys & Qt::ShiftModifier) { 0581 s += QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText); 0582 } 0583 if (modifierKeys & Qt::KeypadModifier) { 0584 s += QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText); 0585 } 0586 0587 } else if (nKey == 0) { 0588 s = i18nc("What the user inputs now will be taken as the new shortcut", "Input"); 0589 } 0590 //make it clear that input is still going on 0591 s.append(QStringLiteral(" ...")); 0592 } 0593 0594 if (s.isEmpty()) { 0595 s = i18nc("No shortcut defined", "None"); 0596 } 0597 0598 s.prepend(QLatin1Char(' ')); 0599 s.append(QLatin1Char(' ')); 0600 keyButton->setText(s); 0601 0602 } 0603 0604 KKeySequenceButton::~KKeySequenceButton() 0605 { 0606 } 0607 0608 //prevent Qt from special casing Tab and Backtab 0609 bool KKeySequenceButton::event(QEvent *e) 0610 { 0611 if (d->isRecording && e->type() == QEvent::KeyPress) { 0612 keyPressEvent(static_cast<QKeyEvent *>(e)); 0613 return true; 0614 } 0615 0616 // The shortcut 'alt+c' ( or any other dialog local action shortcut ) 0617 // ended the recording and triggered the action associated with the 0618 // action. In case of 'alt+c' ending the dialog. It seems that those 0619 // ShortcutOverride events get sent even if grabKeyboard() is active. 0620 if (d->isRecording && e->type() == QEvent::ShortcutOverride) { 0621 e->accept(); 0622 return true; 0623 } 0624 0625 if (d->isRecording && e->type() == QEvent::ContextMenu) { 0626 // is caused by Qt::Key_Menu 0627 e->accept(); 0628 return true; 0629 } 0630 0631 return QPushButton::event(e); 0632 } 0633 0634 void KKeySequenceButton::keyPressEvent(QKeyEvent *e) 0635 { 0636 int keyQt = e->key(); 0637 if (keyQt == -1) { 0638 // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key. 0639 // We cannot do anything useful with those (several keys have -1, indistinguishable) 0640 // and QKeySequence.toString() will also yield a garbage string. 0641 KMessageBox::sorry(this, 0642 i18n("The key you just pressed is not supported by Qt."), 0643 i18n("Unsupported Key")); 0644 return d->cancelRecording(); 0645 } 0646 0647 uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); 0648 0649 //don't have the return or space key appear as first key of the sequence when they 0650 //were pressed to start editing - catch and them and imitate their effect 0651 if (!d->isRecording && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) { 0652 d->startRecording(); 0653 d->modifierKeys = newModifiers; 0654 d->updateShortcutDisplay(); 0655 return; 0656 } 0657 0658 // We get events even if recording isn't active. 0659 if (!d->isRecording) { 0660 return QPushButton::keyPressEvent(e); 0661 } 0662 0663 e->accept(); 0664 d->modifierKeys = newModifiers; 0665 0666 switch (keyQt) { 0667 case Qt::Key_AltGr: //or else we get unicode salad 0668 return; 0669 case Qt::Key_Shift: 0670 case Qt::Key_Control: 0671 case Qt::Key_Alt: 0672 case Qt::Key_Meta: 0673 case Qt::Key_Super_L: 0674 case Qt::Key_Super_R: 0675 d->controlModifierlessTimeout(); 0676 d->updateShortcutDisplay(); 0677 break; 0678 default: 0679 0680 if (d->nKey == 0 && !(d->modifierKeys & ~Qt::SHIFT)) { 0681 // It's the first key and no modifier pressed. Check if this is 0682 // allowed 0683 if (!(KisKKeySequenceWidgetPrivate::isOkWhenModifierless(keyQt) 0684 || d->allowModifierless)) { 0685 // No it's not 0686 return; 0687 } 0688 } 0689 0690 // We now have a valid key press. 0691 if (keyQt) { 0692 /** 0693 * Here is the trap, which is different on every OS. On Widnows Qt 0694 * has incomprehensible rules on translating AltGr-related key 0695 * sequences into shortcuts. On macOS symbols may confuse Qt when 0696 * using Cmd+Shift-based shortcuts. * So we should just ask Qt 0697 * itself what it expects to receive as a shortcut :) 0698 * 0699 * That is exactly what Qt's QKeySequenceEdit does internally. 0700 * 0701 * TODO: in the future replace the whole widget with QKeySequenceEdit, 0702 * it uses QKeyMapper directly. 0703 */ 0704 const QList<int> vec = QKeyMapper::possibleKeys(e); 0705 0706 if (!vec.isEmpty() && e->modifiers() != Qt::NoModifier) { 0707 /** 0708 * Take the first element, which is usually the shortcut form the latin 0709 * layout of the keyboard 0710 */ 0711 keyQt = vec.first(); 0712 } else if ((keyQt == Qt::Key_Backtab) && (d->modifierKeys & Qt::SHIFT)) { 0713 keyQt = Qt::Key_Tab | d->modifierKeys; 0714 } else if (KKeyServer::isShiftAsModifierAllowed(keyQt)) { 0715 keyQt |= d->modifierKeys; 0716 } else { 0717 keyQt |= (d->modifierKeys & ~Qt::SHIFT); 0718 } 0719 0720 if (d->nKey == 0) { 0721 d->keySequence = QKeySequence(keyQt); 0722 } else { 0723 d->keySequence = 0724 KisKKeySequenceWidgetPrivate::appendToSequence(d->keySequence, keyQt); 0725 } 0726 0727 d->nKey++; 0728 if ((!d->multiKeyShortcutsAllowed) || (d->nKey >= 4)) { 0729 d->doneRecording(); 0730 return; 0731 } 0732 d->controlModifierlessTimeout(); 0733 d->updateShortcutDisplay(); 0734 } 0735 } 0736 } 0737 0738 void KKeySequenceButton::keyReleaseEvent(QKeyEvent *e) 0739 { 0740 if (e->key() == -1) { 0741 // ignore garbage, see keyPressEvent() 0742 return; 0743 } 0744 0745 if (!d->isRecording) { 0746 return QPushButton::keyReleaseEvent(e); 0747 } 0748 0749 e->accept(); 0750 0751 uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); 0752 0753 //if a modifier that belongs to the shortcut was released... 0754 if ((newModifiers & d->modifierKeys) < d->modifierKeys) { 0755 d->modifierKeys = newModifiers; 0756 d->controlModifierlessTimeout(); 0757 d->updateShortcutDisplay(); 0758 } 0759 } 0760 0761 //static 0762 QKeySequence KisKKeySequenceWidgetPrivate::appendToSequence(const QKeySequence &seq, int keyQt) 0763 { 0764 switch (seq.count()) { 0765 case 0: 0766 return QKeySequence(keyQt); 0767 case 1: 0768 return QKeySequence(seq[0], keyQt); 0769 case 2: 0770 return QKeySequence(seq[0], seq[1], keyQt); 0771 case 3: 0772 return QKeySequence(seq[0], seq[1], seq[2], keyQt); 0773 default: 0774 return seq; 0775 } 0776 } 0777 0778 //static 0779 bool KisKKeySequenceWidgetPrivate::isOkWhenModifierless(int keyQt) 0780 { 0781 //this whole function is a hack, but especially the first line of code 0782 if (QKeySequence(keyQt).toString().length() == 1) { 0783 return false; 0784 } 0785 0786 switch (keyQt) { 0787 case Qt::Key_Return: 0788 case Qt::Key_Space: 0789 case Qt::Key_Tab: 0790 case Qt::Key_Backtab: //does this ever happen? 0791 case Qt::Key_Backspace: 0792 case Qt::Key_Delete: 0793 return false; 0794 default: 0795 return true; 0796 } 0797 } 0798 0799 #include "moc_kkeysequencewidget.cpp" 0800 #include "moc_kkeysequencewidget_p.cpp"