5#include "private/qobject_p.h"
12#include <private/qkeymapper_p.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qscopeguard.h>
23
24
25
26
27
39 QShortcutEntry(QObject *o,
const QKeySequence &k, Qt::ShortcutContext c,
int i,
bool a, QShortcutMap::ContextMatcher m)
46 {
return keySequence < f.keySequence; }
58#ifdef Dump_QShortcutMap
60
61
62static QDebug &operator<<(QDebug &dbg,
const QShortcutEntry *se)
64 QDebugStateSaver saver(dbg);
66 return dbg <<
"QShortcutEntry(0x0)";
68 <<
"QShortcutEntry(" << se->keyseq
69 <<
"), id(" << se->id <<
"), enabled(" << se->enabled <<
"), autorepeat(" << se->autorepeat
70 <<
"), owner(" << se->owner <<
')';
76
77
80 Q_DECLARE_PUBLIC(QShortcutMap)
104
105
106QShortcutMap::QShortcutMap()
107 : d_ptr(
new QShortcutMapPrivate(
this))
113
114
115QShortcutMap::~QShortcutMap()
120
121
122
123int QShortcutMap::addShortcut(QObject *owner,
const QKeySequence &keySequence, Qt::ShortcutContext context, ContextMatcher matcher)
125 Q_ASSERT_X(owner,
"QShortcutMap::addShortcut",
"All shortcuts need an owner");
126 Q_ASSERT_X(!keySequence.isEmpty(),
"QShortcutMap::addShortcut",
"Cannot add keyless shortcuts to map");
129 QShortcutEntry newEntry(owner, keySequence, context, --(d->currentId),
true, matcher);
130 const auto it = std::upper_bound(d->shortcuts.begin(), d->shortcuts.end(), newEntry);
131 d->shortcuts.insert(it, newEntry);
132 qCDebug(lcShortcutMap).nospace()
133 <<
"QShortcutMap::addShortcut(" << owner <<
", "
134 << keySequence <<
", " << context <<
") added shortcut with ID " << d->currentId;
139
140
141
142
143
144
145
147int QShortcutMap::removeShortcut(
int id, QObject *owner,
const QKeySequence &keySequence)
150 int itemsRemoved = 0;
151 bool allOwners = (owner ==
nullptr);
152 bool allKeys = keySequence.isEmpty();
153 bool allIds = id == 0;
155 auto debug = qScopeGuard([&](){
156 qCDebug(lcShortcutMap).nospace()
157 <<
"QShortcutMap::removeShortcut(" << id <<
", " << owner <<
", "
158 << keySequence <<
") removed " << itemsRemoved <<
" shortcuts(s)";
162 if (allOwners && allKeys && allIds) {
163 itemsRemoved = d->shortcuts.size();
164 d->shortcuts.clear();
168 int i = d->shortcuts.size()-1;
171 const QShortcutEntry &entry = d->shortcuts.at(i);
172 int entryId = entry.id;
173 if ((allOwners || entry.owner == owner)
174 && (allIds || entry.id == id)
175 && (allKeys || entry.keySequence == keySequence)) {
176 d->shortcuts.removeAt(i);
187
188
189
190
191
192
193
194int QShortcutMap::setShortcutEnabled(
bool enable,
int id, QObject *owner,
const QKeySequence &keySequence)
197 int itemsChanged = 0;
198 bool allOwners = (owner ==
nullptr);
199 bool allKeys = keySequence.isEmpty();
200 bool allIds = id == 0;
202 int i = d->shortcuts.size()-1;
205 const QShortcutEntry &entry = d->shortcuts.at(i);
206 if ((allOwners || entry.owner == owner)
207 && (allIds || entry.id == id)
208 && (allKeys || entry.keySequence == keySequence)) {
209 d->shortcuts[i].enabled = enable;
216 qCDebug(lcShortcutMap).nospace()
217 <<
"QShortcutMap::setShortcutEnabled(" << enable <<
", " << id <<
", "
218 << owner <<
", " << keySequence <<
") = " << itemsChanged;
223
224
225
226
227
228
229
230int QShortcutMap::setShortcutAutoRepeat(
bool on,
int id, QObject *owner,
const QKeySequence &keySequence)
233 int itemsChanged = 0;
234 bool allOwners = (owner ==
nullptr);
235 bool allKeys = keySequence.isEmpty();
236 bool allIds = id == 0;
238 int i = d->shortcuts.size()-1;
241 QShortcutEntry entry = d->shortcuts.at(i);
242 if ((allOwners || entry.owner == owner)
243 && (allIds || entry.id == id)
244 && (allKeys || entry.keySequence == keySequence)) {
245 d->shortcuts[i].autorepeat = on;
252 qCDebug(lcShortcutMap).nospace()
253 <<
"QShortcutMap::setShortcutAutoRepeat(" << on <<
", " << id <<
", "
254 << owner <<
", " << keySequence <<
") = " << itemsChanged;
259
260
261void QShortcutMap::resetState()
264 d->currentState = QKeySequence::NoMatch;
265 clearSequence(d->currentSequences);
269
270
271QKeySequence::SequenceMatch QShortcutMap::state()
274 return d->currentState;
278
279
280
281
282
283
284
285
286bool QShortcutMap::tryShortcut(QKeyEvent *e)
290 if (e->key() == Qt::Key_unknown)
293 QKeySequence::SequenceMatch previousState = state();
295 switch (nextState(e)) {
296 case QKeySequence::NoMatch:
300 return previousState == QKeySequence::PartialMatch;
301 case QKeySequence::PartialMatch:
305 case QKeySequence::ExactMatch: {
308 const int identicalMatches = d->identicals.size();
313 return identicalMatches > 0;
316 Q_UNREACHABLE_RETURN(
false);
320
321
322
323
324
325QKeySequence::SequenceMatch QShortcutMap::nextState(QKeyEvent *e)
329 if (e->key() >= Qt::Key_Shift &&
330 e->key() <= Qt::Key_ScrollLock)
331 return d->currentState;
333 QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
336 d->identicals.clear();
339 if (result == QKeySequence::NoMatch && (e->modifiers() & Qt::KeypadModifier)) {
341 result = find(e, Qt::KeypadModifier);
343 if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) {
345 if (e->key() == Qt::Key_Backtab) {
346 QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text());
352 if (result == QKeySequence::NoMatch)
353 clearSequence(d->currentSequences);
354 d->currentState = result;
356 qCDebug(lcShortcutMap).nospace() <<
"QShortcutMap::nextState(" << e <<
") = " << result;
362
363
364bool QShortcutMap::hasShortcutForKeySequence(
const QKeySequence &seq)
const
366 Q_D(
const QShortcutMap);
367 QShortcutEntry entry(seq);
368 const auto itEnd = d->shortcuts.cend();
369 auto it = std::lower_bound(d->shortcuts.cbegin(), itEnd, entry);
371 for (;it != itEnd; ++it) {
372 if (entry.keySequence.matches(it->keySequence) == QKeySequence::ExactMatch
373 && (*it).correctContext() && (*it).enabled) {
383
384
385
386
387
388
389QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e,
int ignoredModifiers)
392 if (!d->shortcuts.size())
393 return QKeySequence::NoMatch;
395 createNewSequences(e, d->newEntries, ignoredModifiers);
396 qCDebug(lcShortcutMap) <<
"Possible input sequences:" << d->newEntries;
399 if (d->newEntries == d->currentSequences) {
400 Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().size(),
401 "QShortcutMap::find",
"New sequence to find identical to previous");
402 return QKeySequence::NoMatch;
406 d->identicals.clear();
408 bool partialFound =
false;
409 bool identicalDisabledFound =
false;
410 QList<QKeySequence> okEntries;
411 QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
412 for (
int i = d->newEntries.size()-1; i >= 0 ; --i) {
413 QShortcutEntry entry(d->newEntries.at(i));
414 qCDebug(lcShortcutMap) <<
"Looking for shortcuts matching" << entry.keySequence;
416 QKeySequence::SequenceMatch bestMatchForEntry = QKeySequence::NoMatch;
418 const auto itEnd = d->shortcuts.constEnd();
419 auto it = std::lower_bound(d->shortcuts.constBegin(), itEnd, entry);
420 for (; it != itEnd; ++it) {
421 QKeySequence::SequenceMatch match = entry.keySequence.matches(it->keySequence);
422 qCDebug(lcShortcutMap) <<
" -" << match <<
"for shortcut" << it->keySequence;
426 if (match == QKeySequence::NoMatch)
429 bestMatchForEntry = qMax(bestMatchForEntry, match);
431 if ((*it).correctContext()) {
432 if (match == QKeySequence::ExactMatch) {
434 d->identicals.append(&*it);
436 identicalDisabledFound =
true;
437 }
else if (match == QKeySequence::PartialMatch) {
439 if (d->identicals.size())
443 partialFound |= (*it).enabled;
446 qCDebug(lcShortcutMap) <<
" - But context was not correct";
452 if (bestMatchForEntry > result) {
454 qCDebug(lcShortcutMap) <<
"Found better match (" << d->newEntries <<
"), clearing key sequence list";
456 if (bestMatchForEntry && bestMatchForEntry >= result) {
457 okEntries << d->newEntries.at(i);
458 qCDebug(lcShortcutMap) <<
"Added ok key sequence" << d->newEntries;
462 if (d->identicals.size()) {
463 result = QKeySequence::ExactMatch;
464 }
else if (partialFound) {
465 result = QKeySequence::PartialMatch;
466 }
else if (identicalDisabledFound) {
467 result = QKeySequence::ExactMatch;
469 clearSequence(d->currentSequences);
470 result = QKeySequence::NoMatch;
472 if (result != QKeySequence::NoMatch)
473 d->currentSequences = okEntries;
474 qCDebug(lcShortcutMap) <<
"Returning shortcut match == " << result;
479
480
481
482
483void QShortcutMap::clearSequence(QList<QKeySequence> &ksl)
486 d_func()->newEntries.clear();
490
491
492
493void QShortcutMap::createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl,
int ignoredModifiers)
496 QList<QKeyCombination> possibleKeys = QKeyMapper::possibleKeys(e);
497 qCDebug(lcShortcutMap) <<
"Creating new sequences for" << e
498 <<
"with ignoredModifiers=" << Qt::KeyboardModifiers(ignoredModifiers);
499 int pkTotal = possibleKeys.size();
503 int ssActual = d->currentSequences.size();
504 int ssTotal = qMax(1, ssActual);
506 ksl.resize(pkTotal * ssTotal);
508 int index = ssActual ? d->currentSequences.at(0).count() : 0;
509 for (
int pkNum = 0; pkNum < pkTotal; ++pkNum) {
510 for (
int ssNum = 0; ssNum < ssTotal; ++ssNum) {
511 int i = (pkNum * ssTotal) + ssNum;
512 QKeySequence &curKsl = ksl[i];
514 const QKeySequence &curSeq = d->currentSequences.at(ssNum);
515 curKsl.setKey(curSeq[0], 0);
516 curKsl.setKey(curSeq[1], 1);
517 curKsl.setKey(curSeq[2], 2);
518 curKsl.setKey(curSeq[3], 3);
520 curKsl.setKey(QKeyCombination::fromCombined(0), 0);
521 curKsl.setKey(QKeyCombination::fromCombined(0), 1);
522 curKsl.setKey(QKeyCombination::fromCombined(0), 2);
523 curKsl.setKey(QKeyCombination::fromCombined(0), 3);
525 const int key = possibleKeys.at(pkNum).toCombined();
526 curKsl.setKey(QKeyCombination::fromCombined(key & ~ignoredModifiers), index);
532
533
534int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers)
537 if (modifiers & Qt::ShiftModifier)
539 if (modifiers & Qt::ControlModifier)
541 if (modifiers & Qt::MetaModifier)
543 if (modifiers & Qt::AltModifier)
549
550
551QList<
const QShortcutEntry*> QShortcutMap::matches()
const
553 Q_D(
const QShortcutMap);
554 return d->identicals;
558
559
560void QShortcutMap::dispatchEvent(QKeyEvent *e)
563 if (!d->identicals.size())
566 const QKeySequence &curKey = d->identicals.at(0)->keySequence;
567 if (d->prevSequence != curKey) {
569 d->prevSequence = curKey;
572 const QShortcutEntry *current =
nullptr, *next =
nullptr;
573 int i = 0, enabledShortcuts = 0;
574 QList<
const QShortcutEntry*> ambiguousShortcuts;
575 while(i < d->identicals.size()) {
576 current = d->identicals.at(i);
577 if (current->enabled || !next){
579 if (lcShortcutMap().isDebugEnabled())
580 ambiguousShortcuts.append(current);
581 if (enabledShortcuts > d->ambigCount + 1)
587 d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1);
590 if (!next || (e->isAutoRepeat() && !next->autorepeat))
593 if (lcShortcutMap().isDebugEnabled()) {
594 if (ambiguousShortcuts.size() > 1) {
595 qCDebug(lcShortcutMap) <<
"The following shortcuts are about to be activated ambiguously:";
596 for (
const QShortcutEntry *entry : std::as_const(ambiguousShortcuts))
597 qCDebug(lcShortcutMap).nospace() <<
"- " << entry->keySequence <<
" (belonging to " << entry->owner <<
")";
600 qCDebug(lcShortcutMap).nospace()
601 <<
"QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\""
602 << next->keySequence.toString() <<
"\", " << next->id <<
", "
603 <<
static_cast<
bool>(enabledShortcuts>1) <<
") to object(" << next->owner <<
')';
605 QShortcutEvent se(next->keySequence, next->id, enabledShortcuts > 1);
606 QCoreApplication::sendEvent(
const_cast<QObject *>(next->owner), &se);
609QList<QKeySequence> QShortcutMap::keySequences(
bool getAll)
const
611 Q_D(
const QShortcutMap);
612 QList<QKeySequence> keys;
613 for (
auto sequence : d->shortcuts) {
614 bool addSequence =
false;
615 if (sequence.enabled) {
616 if (getAll || sequence.context == Qt::ApplicationShortcut ||
617 sequence.owner == QGuiApplication::focusObject()) {
620 QObject *possibleWindow = sequence.owner;
621 while (possibleWindow) {
622 if (qobject_cast<QWindow *>(possibleWindow))
624 possibleWindow = possibleWindow->parent();
626 if (possibleWindow == QGuiApplication::focusWindow()) {
627 if (sequence.context == Qt::WindowShortcut) {
629 }
else if (sequence.context == Qt::WidgetWithChildrenShortcut) {
630 QObject *possibleWidget = QGuiApplication::focusObject();
631 while (possibleWidget->parent()) {
632 possibleWidget = possibleWidget->parent();
633 if (possibleWidget == sequence.owner) {
642 keys << sequence.keySequence;
650
651
652
653#if defined(Dump_QShortcutMap)
654void QShortcutMap::dumpMap()
const
656 Q_D(
const QShortcutMap);
657 for (
int i = 0; i < d->shortcuts.size(); ++i)
658 qDebug().nospace() << &(d->shortcuts.at(i));
QList< const QShortcutEntry * > identicals
QKeySequence prevSequence
QList< QKeySequence > newEntries
QList< QShortcutEntry > shortcuts
QList< QKeySequence > currentSequences
QKeySequence::SequenceMatch currentState
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
Q_DECLARE_TYPEINFO(QShortcutEntry, Q_RELOCATABLE_TYPE)
QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i, bool a, QShortcutMap::ContextMatcher m)
bool correctContext() const
QShortcutEntry(const QKeySequence &k)
Qt::ShortcutContext context
bool operator<(const QShortcutEntry &f) const