1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org> |
4 | SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> |
5 | SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> |
6 | SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de> |
7 | |
8 | SPDX-License-Identifier: LGPL-2.0-or-later |
9 | */ |
10 | |
11 | #include "config-xmlgui.h" |
12 | |
13 | #include "kkeysequencewidget.h" |
14 | |
15 | #include "debug.h" |
16 | #include "kactioncollection.h" |
17 | |
18 | #include <QAction> |
19 | #include <QApplication> |
20 | #include <QHBoxLayout> |
21 | #include <QHash> |
22 | #include <QToolButton> |
23 | |
24 | #include <KKeySequenceRecorder> |
25 | #include <KLocalizedString> |
26 | #include <KMessageBox> |
27 | #if HAVE_GLOBALACCEL |
28 | #include <KGlobalAccel> |
29 | #endif |
30 | |
31 | static bool shortcutsConflictWith(const QList<QKeySequence> &shortcuts, const QKeySequence &needle) |
32 | { |
33 | if (needle.isEmpty()) { |
34 | return false; |
35 | } |
36 | |
37 | for (const QKeySequence &sequence : shortcuts) { |
38 | if (sequence.isEmpty()) { |
39 | continue; |
40 | } |
41 | |
42 | if (sequence.matches(seq: needle) != QKeySequence::NoMatch // |
43 | || needle.matches(seq: sequence) != QKeySequence::NoMatch) { |
44 | return true; |
45 | } |
46 | } |
47 | |
48 | return false; |
49 | } |
50 | |
51 | class KKeySequenceWidgetPrivate |
52 | { |
53 | public: |
54 | KKeySequenceWidgetPrivate(KKeySequenceWidget *qq); |
55 | |
56 | void init(); |
57 | |
58 | void updateShortcutDisplay(); |
59 | void startRecording(); |
60 | |
61 | // Conflicts the key sequence @p seq with a current standard shortcut? |
62 | bool conflictWithStandardShortcuts(const QKeySequence &seq); |
63 | // Conflicts the key sequence @p seq with a current local shortcut? |
64 | bool conflictWithLocalShortcuts(const QKeySequence &seq); |
65 | // Conflicts the key sequence @p seq with a current global shortcut? |
66 | bool conflictWithGlobalShortcuts(const QKeySequence &seq); |
67 | |
68 | bool promptStealLocalShortcut(const QList<QAction *> &actions, const QKeySequence &seq); |
69 | bool promptstealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq); |
70 | |
71 | #if HAVE_GLOBALACCEL |
72 | struct KeyConflictInfo { |
73 | QKeySequence key; |
74 | QList<KGlobalShortcutInfo> shortcutInfo; |
75 | }; |
76 | bool promptStealGlobalShortcut(const std::vector<KeyConflictInfo> &shortcuts, const QKeySequence &sequence); |
77 | #endif |
78 | void wontStealShortcut(QAction *item, const QKeySequence &seq); |
79 | |
80 | bool checkAgainstStandardShortcuts() const |
81 | { |
82 | return checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts; |
83 | } |
84 | |
85 | bool checkAgainstGlobalShortcuts() const |
86 | { |
87 | return checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts; |
88 | } |
89 | |
90 | bool checkAgainstLocalShortcuts() const |
91 | { |
92 | return checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts; |
93 | } |
94 | |
95 | // private slot |
96 | void doneRecording(); |
97 | |
98 | // members |
99 | KKeySequenceWidget *const q; |
100 | KKeySequenceRecorder *recorder; |
101 | QHBoxLayout *layout; |
102 | QPushButton *keyButton; |
103 | QToolButton *clearButton; |
104 | |
105 | QKeySequence keySequence; |
106 | QKeySequence oldKeySequence; |
107 | QString componentName; |
108 | |
109 | //! Check the key sequence against KStandardShortcut::find() |
110 | KKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes; |
111 | |
112 | /** |
113 | * The list of action collections to check against for conflict shortcut |
114 | */ |
115 | QList<KActionCollection *> checkActionCollections; |
116 | |
117 | /** |
118 | * The action to steal the shortcut from. |
119 | */ |
120 | QList<QAction *> stealActions; |
121 | }; |
122 | |
123 | KKeySequenceWidgetPrivate::KKeySequenceWidgetPrivate(KKeySequenceWidget *qq) |
124 | : q(qq) |
125 | , layout(nullptr) |
126 | , keyButton(nullptr) |
127 | , clearButton(nullptr) |
128 | , componentName() |
129 | , checkAgainstShortcutTypes(KKeySequenceWidget::LocalShortcuts | KKeySequenceWidget::GlobalShortcuts) |
130 | , stealActions() |
131 | { |
132 | } |
133 | |
134 | void KKeySequenceWidgetPrivate::init() |
135 | { |
136 | layout = new QHBoxLayout(q); |
137 | layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
138 | |
139 | keyButton = new QPushButton(q); |
140 | keyButton->setFocusPolicy(Qt::StrongFocus); |
141 | keyButton->setIcon(QIcon::fromTheme(QStringLiteral("configure" ))); |
142 | keyButton->setToolTip( |
143 | i18nc("@info:tooltip" , |
144 | "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." )); |
145 | layout->addWidget(keyButton); |
146 | |
147 | clearButton = new QToolButton(q); |
148 | layout->addWidget(clearButton); |
149 | |
150 | if (qApp->isLeftToRight()) { |
151 | clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl" ))); |
152 | } else { |
153 | clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-ltr" ))); |
154 | } |
155 | |
156 | recorder = new KKeySequenceRecorder(q->window()->windowHandle(), q); |
157 | recorder->setModifierlessAllowed(false); |
158 | recorder->setMultiKeyShortcutsAllowed(true); |
159 | |
160 | updateShortcutDisplay(); |
161 | } |
162 | |
163 | bool KKeySequenceWidgetPrivate::promptStealLocalShortcut(const QList<QAction *> &actions, const QKeySequence &seq) |
164 | { |
165 | const int listSize = actions.size(); |
166 | |
167 | QString title = i18ncp("%1 is the number of conflicts" , "Shortcut Conflict" , "Shortcut Conflicts" , listSize); |
168 | |
169 | QString conflictingShortcuts; |
170 | for (const QAction *action : actions) { |
171 | conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n" , |
172 | action->shortcut().toString(QKeySequence::NativeText), |
173 | KLocalizedString::removeAcceleratorMarker(action->text())); |
174 | } |
175 | QString message = i18ncp("%1 is the number of ambiguous shortcut clashes (hidden)" , |
176 | "The \"%2\" shortcut is ambiguous with the following shortcut.\n" |
177 | "Do you want to assign an empty shortcut to this action?\n" |
178 | "%3" , |
179 | "The \"%2\" shortcut is ambiguous with the following shortcuts.\n" |
180 | "Do you want to assign an empty shortcut to these actions?\n" |
181 | "%3" , |
182 | listSize, |
183 | seq.toString(QKeySequence::NativeText), |
184 | conflictingShortcuts); |
185 | |
186 | return KMessageBox::warningContinueCancel(parent: q, text: message, title, buttonContinue: KGuiItem(i18nc("@action:button" , "Reassign" ))) == KMessageBox::Continue; |
187 | } |
188 | |
189 | void KKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq) |
190 | { |
191 | QString title(i18nc("@title:window" , "Shortcut conflict" )); |
192 | QString msg( |
193 | i18n("<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>" |
194 | "Please select a different one.</qt>" , |
195 | seq.toString(QKeySequence::NativeText), |
196 | KLocalizedString::removeAcceleratorMarker(item->text()))); |
197 | KMessageBox::error(parent: q, text: msg, title); |
198 | } |
199 | |
200 | bool KKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence) |
201 | { |
202 | if (!(checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts)) { |
203 | return false; |
204 | } |
205 | |
206 | // Add all the actions from the checkActionCollections list to a single list to |
207 | // be able to process them in a single loop below. |
208 | // Note that this can't be done in setCheckActionCollections(), because we |
209 | // keep pointers to the action collections, and between the call to |
210 | // setCheckActionCollections() and this function some actions might already be |
211 | // removed from the collection again. |
212 | QList<QAction *> allActions; |
213 | for (KActionCollection *collection : std::as_const(t&: checkActionCollections)) { |
214 | allActions += collection->actions(); |
215 | } |
216 | |
217 | // Because of multikey shortcuts we can have clashes with many shortcuts. |
218 | // |
219 | // Example 1: |
220 | // |
221 | // Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F' |
222 | // and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as |
223 | // 'activatedAmbiguously()' for obvious reasons. |
224 | // |
225 | // Example 2: |
226 | // |
227 | // Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'. |
228 | // This will shadow 'CTRL-X' for the same reason as above. |
229 | // |
230 | // Example 3: |
231 | // |
232 | // Some weird combination of Example 1 and 2 with three shortcuts using |
233 | // 1/2/3 key shortcuts. I think you can imagine. |
234 | QList<QAction *> conflictingActions; |
235 | |
236 | // find conflicting shortcuts with existing actions |
237 | for (QAction *qaction : std::as_const(t&: allActions)) { |
238 | if (shortcutsConflictWith(shortcuts: qaction->shortcuts(), needle: keySequence)) { |
239 | // A conflict with a KAction. If that action is configurable |
240 | // ask the user what to do. If not reject this keySequence. |
241 | if (KActionCollection::isShortcutsConfigurable(action: qaction)) { |
242 | conflictingActions.append(t: qaction); |
243 | } else { |
244 | wontStealShortcut(item: qaction, seq: keySequence); |
245 | return true; |
246 | } |
247 | } |
248 | } |
249 | |
250 | if (conflictingActions.isEmpty()) { |
251 | // No conflicting shortcuts found. |
252 | return false; |
253 | } |
254 | |
255 | if (promptStealLocalShortcut(actions: conflictingActions, seq: keySequence)) { |
256 | stealActions = conflictingActions; |
257 | // Announce that the user agreed |
258 | for (QAction *stealAction : std::as_const(t&: stealActions)) { |
259 | Q_EMIT q->stealShortcut(seq: keySequence, action: stealAction); |
260 | } |
261 | return false; |
262 | } |
263 | return true; |
264 | } |
265 | |
266 | #if HAVE_GLOBALACCEL |
267 | bool KKeySequenceWidgetPrivate::promptStealGlobalShortcut(const std::vector<KeyConflictInfo> &clashing, const QKeySequence &sequence) |
268 | { |
269 | QString clashingKeys; |
270 | for (const auto &[key, shortcutInfo] : clashing) { |
271 | const QString seqAsString = key.toString(); |
272 | for (const KGlobalShortcutInfo &info : shortcutInfo) { |
273 | clashingKeys += i18n("Shortcut '%1' in Application '%2' for action '%3'\n" , // |
274 | seqAsString, |
275 | info.componentFriendlyName(), |
276 | info.friendlyName()); |
277 | } |
278 | } |
279 | const int hashSize = clashing.size(); |
280 | |
281 | QString message = i18ncp("%1 is the number of conflicts (hidden), %2 is the key sequence of the shortcut that is problematic" , |
282 | "The shortcut '%2' conflicts with the following key combination:\n" , |
283 | "The shortcut '%2' conflicts with the following key combinations:\n" , |
284 | hashSize, |
285 | sequence.toString()); |
286 | message += clashingKeys; |
287 | |
288 | QString title = i18ncp("%1 is the number of shortcuts with which there is a conflict" , |
289 | "Conflict with Registered Global Shortcut" , |
290 | "Conflict with Registered Global Shortcuts" , |
291 | hashSize); |
292 | |
293 | return KMessageBox::warningContinueCancel(parent: q, text: message, title, buttonContinue: KGuiItem(i18nc("@action:button" , "Reassign" ))) == KMessageBox::Continue; |
294 | } |
295 | #endif |
296 | |
297 | bool KKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence) |
298 | { |
299 | #ifdef Q_OS_WIN |
300 | // on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut |
301 | if (KKeySequenceWidget::GlobalShortcuts && keySequence.toString().contains(QLatin1String("F12" ))) { |
302 | QString title = i18n("Reserved Shortcut" ); |
303 | QString message = i18n( |
304 | "The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n" |
305 | "Please choose another one." ); |
306 | |
307 | KMessageBox::error(q, message, title); |
308 | return false; |
309 | } |
310 | #endif |
311 | #if HAVE_GLOBALACCEL |
312 | if (!(checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts)) { |
313 | return false; |
314 | } |
315 | // Global shortcuts are on key+modifier shortcuts. They can clash with |
316 | // each of the keys of a multi key shortcut. |
317 | std::vector<KeyConflictInfo> clashing; |
318 | for (int i = 0; i < keySequence.count(); ++i) { |
319 | QKeySequence keys(keySequence[i]); |
320 | if (!KGlobalAccel::isGlobalShortcutAvailable(seq: keySequence, component: componentName)) { |
321 | clashing.push_back(x: {.key: keySequence, .shortcutInfo: KGlobalAccel::globalShortcutsByKey(seq: keys)}); |
322 | } |
323 | } |
324 | if (clashing.empty()) { |
325 | return false; |
326 | } |
327 | |
328 | if (!promptStealGlobalShortcut(clashing, sequence: keySequence)) { |
329 | return true; |
330 | } |
331 | // The user approved stealing the shortcut. We have to steal |
332 | // it immediately because KAction::setGlobalShortcut() refuses |
333 | // to set a global shortcut that is already used. There is no |
334 | // error it just silently fails. So be nice because this is |
335 | // most likely the first action that is done in the slot |
336 | // listening to keySequenceChanged(). |
337 | KGlobalAccel::stealShortcutSystemwide(seq: keySequence); |
338 | return false; |
339 | #else |
340 | Q_UNUSED(keySequence); |
341 | return false; |
342 | #endif |
343 | } |
344 | |
345 | bool KKeySequenceWidgetPrivate::promptstealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq) |
346 | { |
347 | QString title = i18nc("@title:window" , "Conflict with Standard Application Shortcut" ); |
348 | QString message = i18n( |
349 | "The '%1' key combination is also used for the standard action " |
350 | "\"%2\" that some applications use.\n" |
351 | "Do you really want to use it as a global shortcut as well?" , |
352 | seq.toString(QKeySequence::NativeText), |
353 | KStandardShortcut::label(std)); |
354 | |
355 | return KMessageBox::warningContinueCancel(parent: q, text: message, title, buttonContinue: KGuiItem(i18nc("@action:button" , "Reassign" ))) == KMessageBox::Continue; |
356 | } |
357 | |
358 | bool KKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &seq) |
359 | { |
360 | if (!(checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts)) { |
361 | return false; |
362 | } |
363 | KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySeq: seq); |
364 | if (ssc != KStandardShortcut::AccelNone && !promptstealStandardShortcut(std: ssc, seq)) { |
365 | return true; |
366 | } |
367 | return false; |
368 | } |
369 | |
370 | void KKeySequenceWidgetPrivate::startRecording() |
371 | { |
372 | keyButton->setDown(true); |
373 | recorder->startRecording(); |
374 | updateShortcutDisplay(); |
375 | } |
376 | |
377 | void KKeySequenceWidgetPrivate::doneRecording() |
378 | { |
379 | keyButton->setDown(false); |
380 | stealActions.clear(); |
381 | keyButton->setText(keyButton->text().chopped(n: strlen(s: " ..." ))); |
382 | q->setKeySequence(seq: recorder->currentKeySequence(), val: KKeySequenceWidget::Validate); |
383 | updateShortcutDisplay(); |
384 | } |
385 | |
386 | void KKeySequenceWidgetPrivate::updateShortcutDisplay() |
387 | { |
388 | QString s; |
389 | QKeySequence sequence = recorder->isRecording() ? recorder->currentKeySequence() : keySequence; |
390 | if (!sequence.isEmpty()) { |
391 | s = sequence.toString(format: QKeySequence::NativeText); |
392 | } else if (recorder->isRecording()) { |
393 | s = i18nc("What the user inputs now will be taken as the new shortcut" , "Input" ); |
394 | } else { |
395 | s = i18nc("No shortcut defined" , "None" ); |
396 | } |
397 | |
398 | if (recorder->isRecording()) { |
399 | // make it clear that input is still going on |
400 | s.append(s: QLatin1String(" ..." )); |
401 | } |
402 | |
403 | s = QLatin1Char(' ') + s + QLatin1Char(' '); |
404 | keyButton->setText(s); |
405 | } |
406 | |
407 | KKeySequenceWidget::KKeySequenceWidget(QWidget *parent) |
408 | : QWidget(parent) |
409 | , d(new KKeySequenceWidgetPrivate(this)) |
410 | { |
411 | d->init(); |
412 | setFocusProxy(d->keyButton); |
413 | connect(sender: d->keyButton, signal: &QPushButton::clicked, context: this, slot: &KKeySequenceWidget::captureKeySequence); |
414 | connect(sender: d->clearButton, signal: &QToolButton::clicked, context: this, slot: &KKeySequenceWidget::clearKeySequence); |
415 | |
416 | connect(sender: d->recorder, signal: &KKeySequenceRecorder::currentKeySequenceChanged, context: this, slot: [this] { |
417 | d->updateShortcutDisplay(); |
418 | }); |
419 | connect(sender: d->recorder, signal: &KKeySequenceRecorder::recordingChanged, context: this, slot: [this] { |
420 | if (!d->recorder->isRecording()) { |
421 | d->doneRecording(); |
422 | } |
423 | }); |
424 | } |
425 | |
426 | KKeySequenceWidget::~KKeySequenceWidget() |
427 | { |
428 | delete d; |
429 | } |
430 | |
431 | KKeySequenceWidget::ShortcutTypes KKeySequenceWidget::checkForConflictsAgainst() const |
432 | { |
433 | return d->checkAgainstShortcutTypes; |
434 | } |
435 | |
436 | void KKeySequenceWidget::setComponentName(const QString &componentName) |
437 | { |
438 | d->componentName = componentName; |
439 | } |
440 | |
441 | bool KKeySequenceWidget::multiKeyShortcutsAllowed() const |
442 | { |
443 | return d->recorder->multiKeyShortcutsAllowed(); |
444 | } |
445 | |
446 | void KKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed) |
447 | { |
448 | d->recorder->setMultiKeyShortcutsAllowed(allowed); |
449 | } |
450 | |
451 | void KKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types) |
452 | { |
453 | d->checkAgainstShortcutTypes = types; |
454 | } |
455 | |
456 | void KKeySequenceWidget::setModifierlessAllowed(bool allow) |
457 | { |
458 | d->recorder->setModifierlessAllowed(allow); |
459 | } |
460 | |
461 | bool KKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const |
462 | { |
463 | if (keySequence.isEmpty()) { |
464 | return true; |
465 | } |
466 | return !(d->conflictWithLocalShortcuts(keySequence) // |
467 | || d->conflictWithGlobalShortcuts(keySequence) // |
468 | || d->conflictWithStandardShortcuts(seq: keySequence)); |
469 | } |
470 | |
471 | bool KKeySequenceWidget::isModifierlessAllowed() |
472 | { |
473 | return d->recorder->modifierlessAllowed(); |
474 | } |
475 | |
476 | bool KKeySequenceWidget::modifierOnlyAllowed() const |
477 | { |
478 | return d->recorder->modifierOnlyAllowed(); |
479 | } |
480 | |
481 | void KKeySequenceWidget::setModifierOnlyAllowed(bool allow) |
482 | { |
483 | d->recorder->setModifierOnlyAllowed(allow); |
484 | } |
485 | |
486 | void KKeySequenceWidget::setClearButtonShown(bool show) |
487 | { |
488 | d->clearButton->setVisible(show); |
489 | } |
490 | |
491 | void KKeySequenceWidget::setCheckActionCollections(const QList<KActionCollection *> &actionCollections) |
492 | { |
493 | d->checkActionCollections = actionCollections; |
494 | } |
495 | |
496 | // slot |
497 | void KKeySequenceWidget::captureKeySequence() |
498 | { |
499 | d->recorder->setWindow(window()->windowHandle()); |
500 | d->recorder->startRecording(); |
501 | } |
502 | |
503 | QKeySequence KKeySequenceWidget::keySequence() const |
504 | { |
505 | return d->keySequence; |
506 | } |
507 | |
508 | // slot |
509 | void KKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate) |
510 | { |
511 | if (d->keySequence == seq) { |
512 | return; |
513 | } |
514 | if (validate == Validate && !isKeySequenceAvailable(keySequence: seq)) { |
515 | return; |
516 | } |
517 | d->keySequence = seq; |
518 | d->updateShortcutDisplay(); |
519 | Q_EMIT keySequenceChanged(seq); |
520 | } |
521 | |
522 | // slot |
523 | void KKeySequenceWidget::clearKeySequence() |
524 | { |
525 | setKeySequence(seq: QKeySequence()); |
526 | } |
527 | |
528 | // slot |
529 | void KKeySequenceWidget::applyStealShortcut() |
530 | { |
531 | QSet<KActionCollection *> changedCollections; |
532 | |
533 | for (QAction *stealAction : std::as_const(t&: d->stealActions)) { |
534 | // Stealing a shortcut means setting it to an empty one. |
535 | stealAction->setShortcuts(QList<QKeySequence>()); |
536 | |
537 | // The following code will find the action we are about to |
538 | // steal from and save it's actioncollection. |
539 | KActionCollection *parentCollection = nullptr; |
540 | for (KActionCollection *collection : std::as_const(t&: d->checkActionCollections)) { |
541 | if (collection->actions().contains(t: stealAction)) { |
542 | parentCollection = collection; |
543 | break; |
544 | } |
545 | } |
546 | |
547 | // Remember the changed collection |
548 | if (parentCollection) { |
549 | changedCollections.insert(value: parentCollection); |
550 | } |
551 | } |
552 | |
553 | for (KActionCollection *col : std::as_const(t&: changedCollections)) { |
554 | col->writeSettings(); |
555 | } |
556 | |
557 | d->stealActions.clear(); |
558 | } |
559 | |
560 | bool KKeySequenceWidget::event(QEvent *ev) |
561 | { |
562 | constexpr char _highlight[] = "_kde_highlight_neutral" ; |
563 | |
564 | if (ev->type() == QEvent::DynamicPropertyChange) { |
565 | auto dpev = static_cast<QDynamicPropertyChangeEvent *>(ev); |
566 | if (dpev->propertyName() == _highlight) { |
567 | d->keyButton->setProperty(name: _highlight, value: property(name: _highlight)); |
568 | return true; |
569 | } |
570 | } |
571 | |
572 | return QWidget::event(event: ev); |
573 | } |
574 | |
575 | #include "moc_kkeysequencewidget.cpp" |
576 | |