Krita Source Code Documentation
Loading...
Searching...
No Matches
kkeysequencewidget.cpp
Go to the documentation of this file.
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org>
3 SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org>
4 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
11#include "config-xmlgui.h"
12#include <QAction>
13#include <QKeyEvent>
14#include <QTimer>
15#include <QHBoxLayout>
16#include <QToolButton>
17#include <QApplication>
18#include <QDebug>
19
20#include <klocalizedstring.h>
21#include <kmessagebox.h>
22#include "kactioncollection.h"
23
24#include <kis_icon_utils.h>
25
26#include <QtGui/private/qkeymapper_p.h>
27
28
29uint qHash(const QKeySequence &seq)
30{
31 return qHash(seq.toString());
32}
33
35{
36public:
38
39 void init();
40
41 static QKeySequence appendToSequence(const QKeySequence &seq, int keyQt);
42 static bool isOkWhenModifierless(int keyQt);
43
45 void startRecording();
46
53 bool conflictWithStandardShortcuts(const QKeySequence &seq);
54
59 bool conflictWithLocalShortcuts(const QKeySequence &seq);
60
64 bool conflictWithGlobalShortcuts(const QKeySequence &seq);
65
69 bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq);
70
75
80
85
87 {
88 if (nKey != 0 && !modifierKeys) {
89 // No modifier key pressed currently. Start the timeout
90 modifierlessTimeout.start(600);
91 } else {
92 // A modifier is pressed. Stop the timeout
94 }
95
96 }
97
103
104//private slot
105 void doneRecording(bool validate = true);
106
107//members
109 QHBoxLayout *layout;
111 QToolButton *clearButton;
112
113 QKeySequence keySequence;
114 QKeySequence oldKeySequence;
122
124 KisKKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes;
125
130
135
140
141 bool stealShortcuts(const QList<QAction *> &actions, const QKeySequence &seq);
142 void wontStealShortcut(QAction *item, const QKeySequence &seq);
143
144};
145
147 : q(q)
148 , layout(0)
149 , keyButton(0)
150 , clearButton(0)
151 , allowModifierless(false)
152 , nKey(0)
153 , modifierKeys(0)
154 , isRecording(false)
155 , multiKeyShortcutsAllowed(true)
156 , componentName()
157 , checkAgainstShortcutTypes(KisKKeySequenceWidget::LocalShortcuts | KisKKeySequenceWidget::GlobalShortcuts)
158 , stealActions()
159{}
160
162 const QList<QAction *> &actions,
163 const QKeySequence &seq)
164{
165
166 const int listSize = actions.size();
167
168 QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize);
169
170 QString conflictingShortcuts;
171 Q_FOREACH (const QAction *action, actions) {
172 conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n",
173 action->shortcut().toString(QKeySequence::NativeText),
174 KLocalizedString::removeAcceleratorMarker(action->text()));
175 }
176 QString message = i18ncp("%1 is the number of ambiguous shortcut clashes (hidden)",
177 "The \"%2\" shortcut is ambiguous with the following shortcut.\n"
178 "Do you want to assign an empty shortcut to this action?\n"
179 "%3",
180 "The \"%2\" shortcut is ambiguous with the following shortcuts.\n"
181 "Do you want to assign an empty shortcut to these actions?\n"
182 "%3",
183 listSize,
184 seq.toString(QKeySequence::NativeText),
185 conflictingShortcuts);
186
187 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
188 return false;
189 }
190
191 return true;
192}
193
194void KisKKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq)
195{
196 QString title(i18n("Shortcut conflict"));
197 QString msg(i18n("<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>"
198 "Please select a different one.</qt>", seq.toString(QKeySequence::NativeText),
199 KLocalizedString::removeAcceleratorMarker(item->text())));
200 KMessageBox::error(q, msg, title);
201}
202
204 : QWidget(parent),
206{
207 d->init();
208 setFocusProxy(d->keyButton);
209 connect(d->keyButton, SIGNAL(clicked()), this, SLOT(captureKeySequence()));
210 connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearKeySequence()));
211 connect(&d->modifierlessTimeout, SIGNAL(timeout()), this, SLOT(doneRecording()));
212 //TODO: how to adopt style changes at runtime?
213 /*QFont modFont = d->clearButton->font();
214 modFont.setStyleHint(QFont::TypeWriter);
215 d->clearButton->setFont(modFont);*/
217}
218
220{
221 layout = new QHBoxLayout(q);
222 layout->setContentsMargins(0, 0, 0, 0);
223
224 keyButton = new KKeySequenceButton(this, q);
225 keyButton->setFocusPolicy(Qt::StrongFocus);
226 keyButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("configure")));
227 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."));
228 layout->addWidget(keyButton);
229
230 clearButton = new QToolButton(q);
231 layout->addWidget(clearButton);
232
233 clearButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("edit-clear")));
234}
235
240
241KisKKeySequenceWidget::ShortcutTypes KisKKeySequenceWidget::checkForConflictsAgainst() const
242{
244}
245
246void KisKKeySequenceWidget::setComponentName(const QString &componentName)
247{
248 d->componentName = componentName;
249}
250
255
260
265
270
271bool KisKKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const
272{
273 if (keySequence.isEmpty()) {
274 // qDebug() << "Key sequence" << keySequence.toString() << "is empty and available.";
275 return true;
276 }
277
278 bool hasConflict = (d->conflictWithLocalShortcuts(keySequence)
281
282 if (hasConflict) {
283 /* qInfo() << "Key sequence" << keySequence.toString() << "has an unresolvable conflict." <<
284 QString("Local conflict: %1. Windows conflict: %2. Standard Shortcut conflict: %3") \
285 .arg(d->conflictWithLocalShortcuts(keySequence)) \
286 .arg(d->conflictWithGlobalShortcuts(keySequence)) \
287 .arg(d->conflictWithStandardShortcuts(keySequence)); */
288 }
289 return !(hasConflict);
290
291}
292
297
299{
300 d->clearButton->setVisible(show);
301}
302
304{
305 d->checkActionCollections = actionCollections;
306}
307
308//slot
313
315{
316 return d->keySequence;
317}
318
319//slot
320void KisKKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate)
321{
322 // oldKeySequence holds the key sequence before recording started, if setKeySequence()
323 // is called while not recording then set oldKeySequence to the existing sequence so
324 // that the keySequenceChanged() signal is emitted if the new and previous key
325 // sequences are different
326 if (!d->isRecording) {
328 }
329
330 d->keySequence = seq;
331 d->doneRecording(validate == Validate);
332}
333
334//slot
336{
337 setKeySequence(QKeySequence());
338}
339
340//slot
342{
343 QSet<KisKActionCollection *> changedCollections;
344
345 Q_FOREACH (QAction *stealAction, d->stealActions) {
346
347 // Stealing a shortcut means setting it to an empty one.
348 stealAction->setShortcuts(QList<QKeySequence>());
349
350 // The following code will find the action we are about to
351 // steal from and save its action collection.
352 KisKActionCollection *parentCollection = 0;
353 foreach (KisKActionCollection *collection, d->checkActionCollections) {
354 if (collection->actions().contains(stealAction)) {
355 parentCollection = collection;
356 break;
357 }
358 }
359
360 // Remember the changed collection
361 if (parentCollection) {
362 changedCollections.insert(parentCollection);
363 }
364 }
365
366 Q_FOREACH (KisKActionCollection *col, changedCollections) {
367 col->writeSettings();
368 }
369
370 d->stealActions.clear();
371}
372
374{
375 nKey = 0;
376 modifierKeys = 0;
378 keySequence = QKeySequence();
379 isRecording = true;
380 keyButton->grabKeyboard();
381
382 if (!QWidget::keyboardGrabber()) {
383 qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active";
384 }
385
386 keyButton->setDown(true);
388}
389
391{
392 modifierlessTimeout.stop();
393 isRecording = false;
394 keyButton->releaseKeyboard();
395 keyButton->setDown(false);
396 stealActions.clear();
397
399 // The sequence hasn't changed
401 return;
402 }
403
404 if (validate && !q->isKeySequenceAvailable(keySequence)) {
405 // The sequence had conflicts and the user said no to stealing it
407 } else {
409 }
410
412}
413
415{
416 // This could hold some OS-specific stuff, or it could be linked back with
417 // the KDE global shortcut code at some point in the future.
418
419#ifdef Q_OS_WIN
420#else
421#endif
422 Q_UNUSED(keySequence);
423
424 return false;
425}
426
427bool shortcutsConflictWith(const QList<QKeySequence> &shortcuts, const QKeySequence &needle)
428{
429 if (needle.isEmpty() || needle.toString(QKeySequence::NativeText).isEmpty()) {
430 return false;
431 }
432
433 foreach (const QKeySequence &sequence, shortcuts) {
434 if (sequence.isEmpty()) {
435 continue;
436 }
437
438 if (sequence.matches(needle) != QKeySequence::NoMatch
439 || needle.matches(sequence) != QKeySequence::NoMatch) {
440 return true;
441 }
442 }
443
444 return false;
445}
446
448{
450 return false;
451 }
452
453 // We have actions both in the deprecated checkList and the
454 // checkActionCollections list. Add all the actions to a single list to
455 // be able to process them in a single loop below.
456 // Note that this can't be done in setCheckActionCollections(), because we
457 // keep pointers to the action collections, and between the call to
458 // setCheckActionCollections() and this function some actions might already be
459 // removed from the collection again.
460 QList<QAction *> allActions;
461 allActions += checkList;
462 foreach (KisKActionCollection *collection, checkActionCollections) {
463 allActions += collection->actions();
464 }
465
466 // Because of multikey shortcuts we can have clashes with many shortcuts.
467 //
468 // Example 1:
469 //
470 // Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F'
471 // and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as
472 // 'activatedAmbiguously()' for obvious reasons.
473 //
474 // Example 2:
475 //
476 // Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'.
477 // This will shadow 'CTRL-X' for the same reason as above.
478 //
479 // Example 3:
480 //
481 // Some weird combination of Example 1 and 2 with three shortcuts using
482 // 1/2/3 key shortcuts. I think you can imagine.
483 QList<QAction *> conflictingActions;
484
485 //find conflicting shortcuts with existing actions
486 foreach (QAction *qaction, allActions) {
487 if (shortcutsConflictWith(qaction->shortcuts(), keySequence)) {
488 // A conflict with a KAction. If that action is configurable
489 // ask the user what to do. If not reject this keySequence.
490 if (checkActionCollections.first()->isShortcutsConfigurable(qaction)) {
491 conflictingActions.append(qaction);
492 } else {
494 return true;
495 }
496 }
497 }
498
499 if (conflictingActions.isEmpty()) {
500 // No conflicting shortcuts found.
501 return false;
502 }
503
504 if (stealShortcuts(conflictingActions, keySequence)) {
505 stealActions = conflictingActions;
506
507 // Announce that the user agreed to override the other shortcut
508 Q_FOREACH (QAction *stealAction, stealActions) {
509 Q_EMIT q->stealShortcut(
511 stealAction);
512 }
513 return false;
514 } else {
515 return true;
516 }
517}
518
520{
522 return false;
523 }
524 KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence);
525 if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) {
526 return true;
527 }
528 return false;
529}
530
531bool KisKKeySequenceWidgetPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
532{
533 QString title = i18n("Conflict with Standard Application Shortcut");
534 QString message = i18n("The '%1' key combination is also used for the standard action "
535 "\"%2\" that some applications use.\n"
536 "Do you really want to use it as a global shortcut as well?",
537 seq.toString(QKeySequence::NativeText), KStandardShortcut::label(std));
538
539 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
540 return false;
541 }
542 return true;
543}
544
546{
547 //empty string if no non-modifier was pressed
548 QString s = keySequence.toString(QKeySequence::NativeText);
549 s.replace(QLatin1Char('&'), QStringLiteral("&&"));
550
551 if (isRecording) {
552 if (modifierKeys) {
553 if (!s.isEmpty()) {
554 s.append(QLatin1Char(','));
555 }
556 if (modifierKeys & Qt::MetaModifier) {
557 s += QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText);
558 }
559#if defined(Q_OS_MAC)
560 if (modifierKeys & Qt::AltModifier) {
561 s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText);
562 }
563 if (modifierKeys & Qt::ControlModifier) {
564 s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
565 }
566#else
567 if (modifierKeys & Qt::ControlModifier) {
568 s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
569 }
570 if (modifierKeys & Qt::AltModifier) {
571 s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText);
572 }
573#endif
574 if (modifierKeys & Qt::ShiftModifier) {
575 s += QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText);
576 }
577 if (modifierKeys & Qt::KeypadModifier) {
578 s += QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText);
579 }
580
581 } else if (nKey == 0) {
582 s = i18nc("What the user inputs now will be taken as the new shortcut", "Input");
583 }
584 //make it clear that input is still going on
585 s.append(QStringLiteral(" ..."));
586 }
587
588 if (s.isEmpty()) {
589 s = i18nc("No shortcut defined", "None");
590 }
591
592 s.prepend(QLatin1Char(' '));
593 s.append(QLatin1Char(' '));
594 keyButton->setText(s);
595
596}
597
601
602//prevent Qt from special casing Tab and Backtab
604{
605 if (d->isRecording && e->type() == QEvent::KeyPress) {
606 keyPressEvent(static_cast<QKeyEvent *>(e));
607 return true;
608 }
609
610 // The shortcut 'alt+c' ( or any other dialog local action shortcut )
611 // ended the recording and triggered the action associated with the
612 // action. In case of 'alt+c' ending the dialog. It seems that those
613 // ShortcutOverride events get sent even if grabKeyboard() is active.
614 if (d->isRecording && e->type() == QEvent::ShortcutOverride) {
615 e->accept();
616 return true;
617 }
618
619 if (d->isRecording && e->type() == QEvent::ContextMenu) {
620 // is caused by Qt::Key_Menu
621 e->accept();
622 return true;
623 }
624
625 return QPushButton::event(e);
626}
627
628namespace {
629
630// Copied here from KKeyServer
631static bool isShiftAsModifierAllowed(int keyQt)
632{
633 // remove any modifiers
634 keyQt &= ~Qt::KeyboardModifierMask;
635
636 // Shift only works as a modifier with certain keys. It's not possible
637 // to enter the SHIFT+5 key sequence for me because this is handled as
638 // '%' by qt on my keyboard.
639 // The working keys are all hardcoded here :-(
640 if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) {
641 return true;
642 }
643
644 if (QChar::isLetter(keyQt)) {
645 return true;
646 }
647
648 switch (keyQt) {
649 case Qt::Key_Return:
650 case Qt::Key_Space:
651 case Qt::Key_Backspace:
652 case Qt::Key_Tab:
653 case Qt::Key_Backtab:
654 case Qt::Key_Escape:
655 case Qt::Key_Print:
656 case Qt::Key_ScrollLock:
657 case Qt::Key_Pause:
658 case Qt::Key_PageUp:
659 case Qt::Key_PageDown:
660 case Qt::Key_Insert:
661 case Qt::Key_Delete:
662 case Qt::Key_Home:
663 case Qt::Key_End:
664 case Qt::Key_Up:
665 case Qt::Key_Down:
666 case Qt::Key_Left:
667 case Qt::Key_Right:
668 case Qt::Key_Enter:
669 case Qt::Key_SysReq:
670 case Qt::Key_CapsLock:
671 case Qt::Key_NumLock:
672 case Qt::Key_Help:
673 case Qt::Key_Back:
674 case Qt::Key_Forward:
675 case Qt::Key_Stop:
676 case Qt::Key_Refresh:
677 case Qt::Key_Favorites:
678 case Qt::Key_LaunchMedia:
679 case Qt::Key_OpenUrl:
680 case Qt::Key_HomePage:
681 case Qt::Key_Search:
682 case Qt::Key_VolumeDown:
683 case Qt::Key_VolumeMute:
684 case Qt::Key_VolumeUp:
685 case Qt::Key_BassBoost:
686 case Qt::Key_BassUp:
687 case Qt::Key_BassDown:
688 case Qt::Key_TrebleUp:
689 case Qt::Key_TrebleDown:
690 case Qt::Key_MediaPlay:
691 case Qt::Key_MediaStop:
692 case Qt::Key_MediaPrevious:
693 case Qt::Key_MediaNext:
694 case Qt::Key_MediaRecord:
695 case Qt::Key_MediaPause:
696 case Qt::Key_MediaTogglePlayPause:
697 case Qt::Key_LaunchMail:
698 case Qt::Key_Calculator:
699 case Qt::Key_Memo:
700 case Qt::Key_ToDoList:
701 case Qt::Key_Calendar:
702 case Qt::Key_PowerDown:
703 case Qt::Key_ContrastAdjust:
704 case Qt::Key_Standby:
705 case Qt::Key_MonBrightnessUp:
706 case Qt::Key_MonBrightnessDown:
707 case Qt::Key_KeyboardLightOnOff:
708 case Qt::Key_KeyboardBrightnessUp:
709 case Qt::Key_KeyboardBrightnessDown:
710 case Qt::Key_PowerOff:
711 case Qt::Key_WakeUp:
712 case Qt::Key_Eject:
713 case Qt::Key_ScreenSaver:
714 case Qt::Key_WWW:
715 case Qt::Key_Sleep:
716 case Qt::Key_LightBulb:
717 case Qt::Key_Shop:
718 case Qt::Key_History:
719 case Qt::Key_AddFavorite:
720 case Qt::Key_HotLinks:
721 case Qt::Key_BrightnessAdjust:
722 case Qt::Key_Finance:
723 case Qt::Key_Community:
724 case Qt::Key_AudioRewind:
725 case Qt::Key_BackForward:
726 case Qt::Key_ApplicationLeft:
727 case Qt::Key_ApplicationRight:
728 case Qt::Key_Book:
729 case Qt::Key_CD:
730 case Qt::Key_Clear:
731 case Qt::Key_ClearGrab:
732 case Qt::Key_Close:
733 case Qt::Key_Copy:
734 case Qt::Key_Cut:
735 case Qt::Key_Display:
736 case Qt::Key_DOS:
737 case Qt::Key_Documents:
738 case Qt::Key_Excel:
739 case Qt::Key_Explorer:
740 case Qt::Key_Game:
741 case Qt::Key_Go:
742 case Qt::Key_iTouch:
743 case Qt::Key_LogOff:
744 case Qt::Key_Market:
745 case Qt::Key_Meeting:
746 case Qt::Key_MenuKB:
747 case Qt::Key_MenuPB:
748 case Qt::Key_MySites:
749 case Qt::Key_News:
750 case Qt::Key_OfficeHome:
751 case Qt::Key_Option:
752 case Qt::Key_Paste:
753 case Qt::Key_Phone:
754 case Qt::Key_Reply:
755 case Qt::Key_Reload:
756 case Qt::Key_RotateWindows:
757 case Qt::Key_RotationPB:
758 case Qt::Key_RotationKB:
759 case Qt::Key_Save:
760 case Qt::Key_Send:
761 case Qt::Key_Spell:
762 case Qt::Key_SplitScreen:
763 case Qt::Key_Support:
764 case Qt::Key_TaskPane:
765 case Qt::Key_Terminal:
766 case Qt::Key_Tools:
767 case Qt::Key_Travel:
768 case Qt::Key_Video:
769 case Qt::Key_Word:
770 case Qt::Key_Xfer:
771 case Qt::Key_ZoomIn:
772 case Qt::Key_ZoomOut:
773 case Qt::Key_Away:
774 case Qt::Key_Messenger:
775 case Qt::Key_WebCam:
776 case Qt::Key_MailForward:
777 case Qt::Key_Pictures:
778 case Qt::Key_Music:
779 case Qt::Key_Battery:
780 case Qt::Key_Bluetooth:
781 case Qt::Key_WLAN:
782 case Qt::Key_UWB:
783 case Qt::Key_AudioForward:
784 case Qt::Key_AudioRepeat:
785 case Qt::Key_AudioRandomPlay:
786 case Qt::Key_Subtitle:
787 case Qt::Key_AudioCycleTrack:
788 case Qt::Key_Time:
789 case Qt::Key_Select:
790 case Qt::Key_View:
791 case Qt::Key_TopMenu:
792 case Qt::Key_Suspend:
793 case Qt::Key_Hibernate:
794 case Qt::Key_Launch0:
795 case Qt::Key_Launch1:
796 case Qt::Key_Launch2:
797 case Qt::Key_Launch3:
798 case Qt::Key_Launch4:
799 case Qt::Key_Launch5:
800 case Qt::Key_Launch6:
801 case Qt::Key_Launch7:
802 case Qt::Key_Launch8:
803 case Qt::Key_Launch9:
804 case Qt::Key_LaunchA:
805 case Qt::Key_LaunchB:
806 case Qt::Key_LaunchC:
807 case Qt::Key_LaunchD:
808 case Qt::Key_LaunchE:
809 case Qt::Key_LaunchF:
810 case Qt::Key_Shift:
811 case Qt::Key_Control:
812 case Qt::Key_Meta:
813 case Qt::Key_Alt:
814 case Qt::Key_Super_L:
815 case Qt::Key_Super_R:
816 return true;
817
818 default:
819 return false;
820 }
821}
822
823} // namespace
824
826{
827 int keyQt = e->key();
828 if (keyQt == -1) {
829 // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key.
830 // We cannot do anything useful with those (several keys have -1, indistinguishable)
831 // and QKeySequence.toString() will also yield a garbage string.
832 KMessageBox::error(this,
833 i18n("The key you just pressed is not supported by Qt."),
834 i18n("Unsupported Key"));
835 return d->cancelRecording();
836 }
837
838 uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
839
840 //don't have the return or space key appear as first key of the sequence when they
841 //were pressed to start editing - catch and them and imitate their effect
842 if (!d->isRecording && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) {
843 d->startRecording();
844 d->modifierKeys = newModifiers;
846 return;
847 }
848
849 // We get events even if recording isn't active.
850 if (!d->isRecording) {
851 return QPushButton::keyPressEvent(e);
852 }
853
854 e->accept();
855 d->modifierKeys = newModifiers;
856
857 switch (keyQt) {
858 case Qt::Key_AltGr: //or else we get unicode salad
859 return;
860 case Qt::Key_Shift:
861 case Qt::Key_Control:
862 case Qt::Key_Alt:
863 case Qt::Key_Meta:
864 case Qt::Key_Super_L:
865 case Qt::Key_Super_R:
868 break;
869 default:
870
871 if (d->nKey == 0 && !(d->modifierKeys & ~Qt::SHIFT)) {
872 // It's the first key and no modifier pressed. Check if this is
873 // allowed
875 || d->allowModifierless)) {
876 // No it's not
877 return;
878 }
879 }
880
881 // We now have a valid key press.
882 if (keyQt) {
895#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
896 const QList<int> vec = QKeyMapper::possibleKeys(e);
897#else
898 const QList<QKeyCombination> vec = QKeyMapper::possibleKeys(e);
899#endif
900
901 if (!vec.isEmpty() && e->modifiers() != Qt::NoModifier) {
906 keyQt = vec.first();
907 } else if ((keyQt == Qt::Key_Backtab) && (d->modifierKeys & Qt::SHIFT)) {
908 keyQt = Qt::Key_Tab | d->modifierKeys;
909 } else if (isShiftAsModifierAllowed(keyQt)) {
910 keyQt |= d->modifierKeys;
911 } else {
912 keyQt |= (d->modifierKeys & ~Qt::SHIFT);
913 }
914
915 if (d->nKey == 0) {
916 d->keySequence = QKeySequence(keyQt);
917 } else {
918 d->keySequence =
920 }
921
922 d->nKey++;
923 if ((!d->multiKeyShortcutsAllowed) || (d->nKey >= 4)) {
924 d->doneRecording();
925 return;
926 }
929 }
930 }
931}
932
934{
935 if (e->key() == -1) {
936 // ignore garbage, see keyPressEvent()
937 return;
938 }
939
940 if (!d->isRecording) {
941 return QPushButton::keyReleaseEvent(e);
942 }
943
944 e->accept();
945
946 uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
947
948#ifdef Q_OS_ANDROID
949 // On Android, releasing a modifier will still carry the modifiers on the
950 // release event, so we have to clear those out explicitly.
951 switch (e->key()) {
952 case Qt::Key_Shift:
953 newModifiers &= ~Qt::SHIFT;
954 break;
955 case Qt::Key_Control:
956 newModifiers &= ~Qt::CTRL;
957 break;
958 case Qt::Key_Alt:
959 case Qt::Key_AltGr:
960 newModifiers &= ~Qt::ALT;
961 break;
962 case Qt::Key_Meta:
963 newModifiers &= ~Qt::META;
964 break;
965 }
966#endif
967
968 //if a modifier that belongs to the shortcut was released...
969 if ((newModifiers & d->modifierKeys) < d->modifierKeys) {
970 d->modifierKeys = newModifiers;
973 }
974}
975
976//static
977QKeySequence KisKKeySequenceWidgetPrivate::appendToSequence(const QKeySequence &seq, int keyQt)
978{
979 switch (seq.count()) {
980 case 0:
981 return QKeySequence(keyQt);
982 case 1:
983 return QKeySequence(seq[0], keyQt);
984 case 2:
985 return QKeySequence(seq[0], seq[1], keyQt);
986 case 3:
987 return QKeySequence(seq[0], seq[1], seq[2], keyQt);
988 default:
989 return seq;
990 }
991}
992
993//static
995{
996 //this whole function is a hack, but especially the first line of code
997 if (QKeySequence(keyQt).toString().length() == 1) {
998 return false;
999 }
1000
1001 switch (keyQt) {
1002 case Qt::Key_Return:
1003 case Qt::Key_Space:
1004 case Qt::Key_Tab:
1005 case Qt::Key_Backtab: //does this ever happen?
1006 case Qt::Key_Backspace:
1007 case Qt::Key_Delete:
1008 return false;
1009 default:
1010 return true;
1011 }
1012}
1013
1014#include "moc_kkeysequencewidget.cpp"
1015#include "moc_kkeysequencewidget_p.cpp"
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
unsigned int uint
bool event(QEvent *event) override
KisKKeySequenceWidgetPrivate *const d
void keyReleaseEvent(QKeyEvent *event) override
void keyPressEvent(QKeyEvent *event) override
A container for a set of QAction objects.
void writeSettings(KConfigGroup *config=0, bool writeScheme=false, QAction *oneAction=0) const
QList< QAction * > actions() const
KisKKeySequenceWidget *const q
bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
QList< KisKActionCollection * > checkActionCollections
bool stealShortcuts(const QList< QAction * > &actions, const QKeySequence &seq)
bool conflictWithStandardShortcuts(const QKeySequence &seq)
bool conflictWithGlobalShortcuts(const QKeySequence &seq)
static bool isOkWhenModifierless(int keyQt)
KisKKeySequenceWidgetPrivate(KisKKeySequenceWidget *q)
void doneRecording(bool validate=true)
static QKeySequence appendToSequence(const QKeySequence &seq, int keyQt)
bool conflictWithLocalShortcuts(const QKeySequence &seq)
void wontStealShortcut(QAction *item, const QKeySequence &seq)
KisKKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes
Check the key sequence against KStandardShortcut::find()
A widget to input a QKeySequence.
KisKKeySequenceWidget(QWidget *parent=0)
void setComponentName(const QString &componentName)
ShortcutTypes checkForConflictsAgainst
void setClearButtonShown(bool show)
void keySequenceChanged(const QKeySequence &seq)
void setModifierlessAllowed(bool allow)
void setCheckActionCollections(const QList< KisKActionCollection * > &actionCollections)
KisKKeySequenceWidgetPrivate *const d
@ GlobalShortcuts
Check against global shortcuts.
@ StandardShortcuts
Check against standard shortcuts.
@ LocalShortcuts
Check with local shortcuts.
void setKeySequence(const QKeySequence &seq, Validation val=NoValidate)
void stealShortcut(const QKeySequence &seq, QAction *action)
QKeySequence keySequence() const
@ Validate
Validate key sequence.
bool isKeySequenceAvailable(const QKeySequence &seq) const
void setCheckForConflictsAgainst(ShortcutTypes types)
bool shortcutsConflictWith(const QList< QKeySequence > &shortcuts, const QKeySequence &needle)
uint qHash(const QKeySequence &seq)
QIcon loadIcon(const QString &name)