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 | |