1/*
2 SPDX-FileCopyrightText: 2020 David Redondo <davidedmundson@kde.org>
3 SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org>
4 SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org>
5 SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org>
6 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
7
8 SPDX-License-Identifier: LGPL-2.1-or-later
9*/
10
11#include "keysequencehelper.h"
12
13#include <QDebug>
14#include <QHash>
15#include <QPointer>
16#include <QQmlEngine>
17#include <QQuickRenderControl>
18#include <QQuickWindow>
19
20#include <KLocalizedString>
21#include <KStandardShortcut>
22
23#ifndef Q_OS_ANDROID
24#include <KMessageBox>
25#endif
26
27#include <config-kdeclarative.h>
28#if HAVE_KGLOBALACCEL
29#include <KGlobalAccel>
30#include <KGlobalShortcutInfo>
31#endif
32
33class KeySequenceHelperPrivate
34{
35public:
36 KeySequenceHelperPrivate(KeySequenceHelper *qq);
37
38 /**
39 * Conflicts the key sequence @a seq with a current standard
40 * shortcut?
41 */
42 bool conflictWithStandardShortcuts(const QKeySequence &seq);
43
44 /**
45 * Conflicts the key sequence @a seq with a current global
46 * shortcut?
47 */
48 bool conflictWithGlobalShortcuts(const QKeySequence &seq);
49
50 /**
51 * Get permission to steal the shortcut @seq from the standard shortcut @a std.
52 */
53 bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq);
54
55 bool checkAgainstStandardShortcuts() const
56 {
57 return checkAgainstShortcutTypes & KeySequenceHelper::StandardShortcuts;
58 }
59
60 bool checkAgainstGlobalShortcuts() const
61 {
62 return checkAgainstShortcutTypes & KeySequenceHelper::GlobalShortcuts;
63 }
64
65 // members
66 KeySequenceHelper *const q;
67
68 //! Check the key sequence against KStandardShortcut::find()
69 KeySequenceHelper::ShortcutTypes checkAgainstShortcutTypes;
70};
71
72KeySequenceHelperPrivate::KeySequenceHelperPrivate(KeySequenceHelper *qq)
73 : q(qq)
74 , checkAgainstShortcutTypes(KeySequenceHelper::StandardShortcuts | KeySequenceHelper::GlobalShortcuts)
75{
76}
77
78KeySequenceHelper::KeySequenceHelper(QObject *parent)
79 : KKeySequenceRecorder(nullptr, parent)
80 , d(new KeySequenceHelperPrivate(this))
81{
82}
83
84KeySequenceHelper::~KeySequenceHelper()
85{
86 delete d;
87}
88
89bool KeySequenceHelper::isKeySequenceAvailable(const QKeySequence &keySequence) const
90{
91 if (keySequence.isEmpty()) {
92 return true;
93 }
94 bool conflict = false;
95 if (d->checkAgainstShortcutTypes.testFlag(flag: GlobalShortcuts)) {
96 conflict |= d->conflictWithGlobalShortcuts(seq: keySequence);
97 }
98 if (d->checkAgainstShortcutTypes.testFlag(flag: StandardShortcuts)) {
99 conflict |= d->conflictWithStandardShortcuts(seq: keySequence);
100 }
101 return !conflict;
102}
103
104KeySequenceHelper::ShortcutTypes KeySequenceHelper::checkAgainstShortcutTypes()
105{
106 return d->checkAgainstShortcutTypes;
107}
108
109void KeySequenceHelper::setCheckAgainstShortcutTypes(KeySequenceHelper::ShortcutTypes types)
110{
111 if (d->checkAgainstShortcutTypes != types) {
112 d->checkAgainstShortcutTypes = types;
113 }
114 Q_EMIT checkAgainstShortcutTypesChanged();
115}
116
117bool KeySequenceHelperPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence)
118{
119#ifdef Q_OS_WIN
120 // on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut
121 if (KeySequenceHelper::GlobalShortcuts && keySequence.toString().contains(QLatin1String("F12"))) {
122 QString title = i18n("Reserved Shortcut");
123 QString message = i18n(
124 "The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n"
125 "Please choose another one.");
126
127 KMessageBox::error(nullptr, message, title);
128 }
129 return false;
130#elif HAVE_KGLOBALACCEL
131 if (!(checkAgainstShortcutTypes & KeySequenceHelper::GlobalShortcuts)) {
132 return false;
133 }
134
135 // Global shortcuts are on key+modifier shortcuts. They can clash with a multi key shortcut.
136 QList<KGlobalShortcutInfo> others;
137 QList<KGlobalShortcutInfo> shadow;
138 QList<KGlobalShortcutInfo> shadowed;
139 if (!KGlobalAccel::isGlobalShortcutAvailable(seq: keySequence, component: QString())) {
140 others << KGlobalAccel::globalShortcutsByKey(seq: keySequence);
141
142 // look for shortcuts shadowing
143 shadow << KGlobalAccel::globalShortcutsByKey(seq: keySequence, type: KGlobalAccel::MatchType::Shadows);
144 shadowed << KGlobalAccel::globalShortcutsByKey(seq: keySequence, type: KGlobalAccel::MatchType::Shadowed);
145 }
146
147 if (!shadow.isEmpty() || !shadowed.isEmpty()) {
148 QString title = i18n("Global Shortcut Shadowing");
149 QString message;
150 if (!shadowed.isEmpty()) {
151 message += i18n("The '%1' key combination is shadowed by following global actions:\n").arg(a: keySequence.toString());
152 for (const KGlobalShortcutInfo &info : std::as_const(t&: shadowed)) {
153 message += i18n("Action '%1' in context '%2'\n").arg(args: info.friendlyName(), args: info.contextFriendlyName());
154 }
155 }
156 if (!shadow.isEmpty()) {
157 message += i18n("The '%1' key combination shadows following global actions:\n").arg(a: keySequence.toString());
158 for (const KGlobalShortcutInfo &info : std::as_const(t&: shadow)) {
159 message += i18n("Action '%1' in context '%2'\n").arg(args: info.friendlyName(), args: info.contextFriendlyName());
160 }
161 }
162
163 KMessageBox::error(parent: nullptr, text: message, title);
164 return true;
165 }
166
167 if (!others.isEmpty() && !KGlobalAccel::promptStealShortcutSystemwide(parent: nullptr, shortcuts: others, seq: keySequence)) {
168 return true;
169 }
170
171 // The user approved stealing the shortcut. We have to steal
172 // it immediately because KAction::setGlobalShortcut() refuses
173 // to set a global shortcut that is already used. There is no
174 // error it just silently fails. So be nice because this is
175 // most likely the first action that is done in the slot
176 // listening to keySequenceChanged().
177 KGlobalAccel::stealShortcutSystemwide(seq: keySequence);
178 return false;
179#else
180 return false;
181#endif
182}
183
184bool KeySequenceHelperPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence)
185{
186 if (!checkAgainstStandardShortcuts()) {
187 return false;
188 }
189
190 KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySeq: keySequence);
191 if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(std: ssc, seq: keySequence)) {
192 return true;
193 }
194 return false;
195}
196
197bool KeySequenceHelperPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
198{
199#ifndef Q_OS_ANDROID
200 QString title = i18n("Conflict with Standard Application Shortcut");
201 QString message = i18n(
202 "The '%1' key combination is also used for the standard action "
203 "\"%2\" that some applications use.\n"
204 "Do you really want to use it as a global shortcut as well?",
205 seq.toString(QKeySequence::NativeText),
206 KStandardShortcut::label(std));
207
208 if (KMessageBox::warningContinueCancel(parent: nullptr, text: message, title, buttonContinue: KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
209 return false;
210 }
211 return true;
212#else
213 return false;
214#endif
215}
216
217bool KeySequenceHelper::keySequenceIsEmpty(const QKeySequence &keySequence)
218{
219 return keySequence.isEmpty();
220}
221
222QString KeySequenceHelper::keySequenceNativeText(const QKeySequence &keySequence)
223{
224 return keySequence.toString(format: QKeySequence::NativeText);
225}
226
227QWindow *KeySequenceHelper::renderWindow(QQuickWindow *quickWindow)
228{
229 QWindow *renderWindow = QQuickRenderControl::renderWindowFor(win: quickWindow);
230 auto window = renderWindow ? renderWindow : quickWindow;
231 // If we have CppOwnership, set it explicitly to prevent the engine taking ownership of the window
232 // and crashing on teardown
233 if (QQmlEngine::objectOwnership(window) == QQmlEngine::CppOwnership) {
234 QQmlEngine::setObjectOwnership(window, QQmlEngine::CppOwnership);
235 }
236 return window;
237}
238
239#include "moc_keysequencehelper.cpp"
240

source code of kdeclarative/src/qmlcontrols/kquickcontrols/private/keysequencehelper.cpp