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"