| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2024 Arjen Hiemstra <ahiemstra@heimr.nl> |
| 3 | SPDX-FileCopyrightText: 2020 David Redondo <davidedmundson@kde.org> |
| 4 | SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org> |
| 5 | SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> |
| 6 | SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> |
| 7 | SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> |
| 8 | |
| 9 | SPDX-License-Identifier: LGPL-2.1-or-later |
| 10 | */ |
| 11 | |
| 12 | #include "keysequencevalidator.h" |
| 13 | |
| 14 | #include <KLocalizedString> |
| 15 | #include <KStandardShortcut> |
| 16 | |
| 17 | #include <config-kdeclarative.h> |
| 18 | #if HAVE_KGLOBALACCEL |
| 19 | #include <KGlobalAccel> |
| 20 | #include <KGlobalShortcutInfo> |
| 21 | #endif |
| 22 | |
| 23 | KeySequenceValidator::KeySequenceValidator(QObject *parent) |
| 24 | : QObject(parent) |
| 25 | { |
| 26 | } |
| 27 | |
| 28 | QKeySequence KeySequenceValidator::currentKeySequence() const |
| 29 | { |
| 30 | return m_currentKeySequence; |
| 31 | } |
| 32 | |
| 33 | void KeySequenceValidator::setCurrentKeySequence(const QKeySequence &sequence) |
| 34 | { |
| 35 | if (m_currentKeySequence == sequence) { |
| 36 | return; |
| 37 | } |
| 38 | |
| 39 | m_currentKeySequence = sequence; |
| 40 | Q_EMIT currentKeySequenceChanged(); |
| 41 | } |
| 42 | |
| 43 | KeySequenceEnums::ShortcutTypes KeySequenceValidator::validateTypes() const |
| 44 | { |
| 45 | return m_validateTypes; |
| 46 | } |
| 47 | |
| 48 | void KeySequenceValidator::setValidateTypes(KeySequenceEnums::ShortcutTypes types) |
| 49 | { |
| 50 | if (m_validateTypes == types) { |
| 51 | return; |
| 52 | } |
| 53 | |
| 54 | m_validateTypes = types; |
| 55 | Q_EMIT validateTypesChanged(); |
| 56 | } |
| 57 | |
| 58 | void KeySequenceValidator::validateSequence(const QKeySequence &keySequence) |
| 59 | { |
| 60 | ValidationResult result = ValidationResult::Accept; |
| 61 | if (m_validateTypes & KeySequenceEnums::GlobalShortcuts) { |
| 62 | result = validateGlobalShortcut(keySequence); |
| 63 | } |
| 64 | |
| 65 | if (result == ValidationResult::Reject) { |
| 66 | Q_EMIT finished(keySequence: m_currentKeySequence); |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | if (result == ValidationResult::QuestionPending) { |
| 71 | return; |
| 72 | } |
| 73 | |
| 74 | if (m_validateTypes & KeySequenceEnums::StandardShortcuts) { |
| 75 | result = validateStandardShortcut(keySequence); |
| 76 | } |
| 77 | |
| 78 | if (result == ValidationResult::Reject) { |
| 79 | Q_EMIT finished(keySequence: m_currentKeySequence); |
| 80 | } else if (result == ValidationResult::Accept) { |
| 81 | Q_EMIT finished(keySequence); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | void KeySequenceValidator::accept() |
| 86 | { |
| 87 | QKeySequence keySequence = m_pendingKeySequence; |
| 88 | m_pendingKeySequence = QKeySequence{}; |
| 89 | |
| 90 | ValidationResult result = ValidationResult::Accept; |
| 91 | if (m_validateGlobalPending) { |
| 92 | m_validateGlobalPending = false; |
| 93 | if (m_validateTypes & KeySequenceEnums::StandardShortcuts) { |
| 94 | result = validateStandardShortcut(keySequence); |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | if (result == ValidationResult::Reject) { |
| 99 | Q_EMIT finished(keySequence: m_currentKeySequence); |
| 100 | } else if (result == ValidationResult::Accept) { |
| 101 | #if HAVE_KGLOBALACCEL |
| 102 | if (m_validateTypes & KeySequenceEnums::GlobalShortcuts) { |
| 103 | KGlobalAccel::stealShortcutSystemwide(seq: keySequence); |
| 104 | } |
| 105 | #endif |
| 106 | Q_EMIT finished(keySequence); |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | void KeySequenceValidator::reject() |
| 111 | { |
| 112 | m_pendingKeySequence = QKeySequence{}; |
| 113 | Q_EMIT finished(keySequence: m_currentKeySequence); |
| 114 | } |
| 115 | |
| 116 | KeySequenceValidator::ValidationResult KeySequenceValidator::validateGlobalShortcut(const QKeySequence &keySequence) |
| 117 | { |
| 118 | #ifdef Q_OS_WIN |
| 119 | // on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut |
| 120 | if (keySequence.toString().contains(QLatin1String("F12" ))) { |
| 121 | QString title = i18n("Reserved Shortcut" ); |
| 122 | QString message = i18n( |
| 123 | "The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n" |
| 124 | "Please choose another one." ); |
| 125 | |
| 126 | Q_EMIT error(title, message); |
| 127 | return ValidationResult::Reject; |
| 128 | } else { |
| 129 | return ValidationResult::Accept; |
| 130 | } |
| 131 | #elif HAVE_KGLOBALACCEL |
| 132 | // Global shortcuts are on key+modifier shortcuts. They can clash with a multi key shortcut. |
| 133 | QList<KGlobalShortcutInfo> others; |
| 134 | QList<KGlobalShortcutInfo> shadow; |
| 135 | QList<KGlobalShortcutInfo> shadowed; |
| 136 | if (!KGlobalAccel::isGlobalShortcutAvailable(seq: keySequence, component: QString())) { |
| 137 | others << KGlobalAccel::globalShortcutsByKey(seq: keySequence); |
| 138 | |
| 139 | // look for shortcuts shadowing |
| 140 | shadow << KGlobalAccel::globalShortcutsByKey(seq: keySequence, type: KGlobalAccel::MatchType::Shadows); |
| 141 | shadowed << KGlobalAccel::globalShortcutsByKey(seq: keySequence, type: KGlobalAccel::MatchType::Shadowed); |
| 142 | } |
| 143 | |
| 144 | if (!shadow.isEmpty() || !shadowed.isEmpty()) { |
| 145 | QString title = i18n("Global Shortcut Shadowing" ); |
| 146 | QString message; |
| 147 | if (!shadowed.isEmpty()) { |
| 148 | message += i18n("The '%1' key combination is shadowed by following global actions:\n" ).arg(a: keySequence.toString()); |
| 149 | for (const KGlobalShortcutInfo &info : std::as_const(t&: shadowed)) { |
| 150 | message += i18n("Action '%1' in context '%2'\n" ).arg(args: info.friendlyName(), args: info.contextFriendlyName()); |
| 151 | } |
| 152 | } |
| 153 | if (!shadow.isEmpty()) { |
| 154 | message += i18n("The '%1' key combination shadows following global actions:\n" ).arg(a: keySequence.toString()); |
| 155 | for (const KGlobalShortcutInfo &info : std::as_const(t&: shadow)) { |
| 156 | message += i18n("Action '%1' in context '%2'\n" ).arg(args: info.friendlyName(), args: info.contextFriendlyName()); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | Q_EMIT error(title, message); |
| 161 | return ValidationResult::Reject; |
| 162 | } |
| 163 | |
| 164 | if (!others.isEmpty()) { |
| 165 | QString title = i18nc("@dialog:title" , "Found Conflict" ); |
| 166 | QString message; |
| 167 | |
| 168 | if (others.size() == 1) { |
| 169 | auto info = others.at(i: 0); |
| 170 | message = i18n("Shortcut '%1' is already assigned to action '%2' of %3.\nDo you want to reassign it?" , |
| 171 | keySequence.toString(), |
| 172 | info.friendlyName(), |
| 173 | info.componentFriendlyName()); |
| 174 | } else { |
| 175 | message = i18n("Shortcut '%1' is already assigned to the following actions:\n" ); |
| 176 | for (const auto &info : std::as_const(t&: others)) { |
| 177 | message += i18n("Action '%1' of %2\n" , info.friendlyName(), info.componentFriendlyName()); |
| 178 | } |
| 179 | message += i18n("Do you want to reassign it?" ); |
| 180 | } |
| 181 | |
| 182 | m_pendingKeySequence = keySequence; |
| 183 | m_validateGlobalPending = true; |
| 184 | Q_EMIT question(title, message); |
| 185 | return ValidationResult::QuestionPending; |
| 186 | } |
| 187 | #endif |
| 188 | return ValidationResult::Accept; |
| 189 | } |
| 190 | |
| 191 | KeySequenceValidator::ValidationResult KeySequenceValidator::validateStandardShortcut(const QKeySequence &keySequence) |
| 192 | { |
| 193 | KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySeq: keySequence); |
| 194 | if (ssc != KStandardShortcut::AccelNone) { |
| 195 | QString title = i18n("Conflict with Standard Application Shortcut" ); |
| 196 | QString message = i18n( |
| 197 | "The '%1' key combination is also used for the standard action " |
| 198 | "\"%2\" that some applications use.\n" |
| 199 | "Do you really want to use it as a global shortcut as well?" , |
| 200 | keySequence.toString(QKeySequence::NativeText), |
| 201 | KStandardShortcut::label(ssc)); |
| 202 | |
| 203 | m_pendingKeySequence = keySequence; |
| 204 | Q_EMIT question(title, message); |
| 205 | return ValidationResult::QuestionPending; |
| 206 | } else { |
| 207 | return ValidationResult::Accept; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | #include "moc_keysequencevalidator.cpp" |
| 212 | |