6#include "private/qobject_p.h"
13#include <private/qkeymapper_p.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/qscopeguard.h>
24
25
26
27
28
40 QShortcutEntry(QObject *o,
const QKeySequence &k, Qt::ShortcutContext c,
int i,
bool a, QShortcutMap::ContextMatcher m)
47 {
return keySequence < f.keySequence; }
59#ifdef Dump_QShortcutMap
61
62
63static QDebug &operator<<(QDebug &dbg,
const QShortcutEntry *se)
65 QDebugStateSaver saver(dbg);
67 return dbg <<
"QShortcutEntry(0x0)";
69 <<
"QShortcutEntry(" << se->keyseq
70 <<
"), id(" << se->id <<
"), enabled(" << se->enabled <<
"), autorepeat(" << se->autorepeat
71 <<
"), owner(" << se->owner <<
')';
77
78
81 Q_DECLARE_PUBLIC(QShortcutMap)
105
106
107QShortcutMap::QShortcutMap()
108 : d_ptr(
new QShortcutMapPrivate(
this))
114
115
116QShortcutMap::~QShortcutMap()
121
122
123
124int QShortcutMap::addShortcut(QObject *owner,
const QKeySequence &keySequence, Qt::ShortcutContext context, ContextMatcher matcher)
126 Q_ASSERT_X(owner,
"QShortcutMap::addShortcut",
"All shortcuts need an owner");
127 Q_ASSERT_X(!keySequence.isEmpty(),
"QShortcutMap::addShortcut",
"Cannot add keyless shortcuts to map");
130 QShortcutEntry newEntry(owner, keySequence, context, --(d->currentId),
true, matcher);
131 const auto it = std::upper_bound(d->shortcuts.begin(), d->shortcuts.end(), newEntry);
132 d->shortcuts.insert(it, newEntry);
133 qCDebug(lcShortcutMap).nospace()
134 <<
"QShortcutMap::addShortcut(" << owner <<
", "
135 << keySequence <<
", " << context <<
") added shortcut with ID " << d->currentId;
140
141
142
143
144
145
146
148int QShortcutMap::removeShortcut(
int id, QObject *owner,
const QKeySequence &keySequence)
151 int itemsRemoved = 0;
152 bool allOwners = (owner ==
nullptr);
153 bool allKeys = keySequence.isEmpty();
154 bool allIds = id == 0;
156 auto debug = qScopeGuard([&](){
157 qCDebug(lcShortcutMap).nospace()
158 <<
"QShortcutMap::removeShortcut(" << id <<
", " << owner <<
", "
159 << keySequence <<
") removed " << itemsRemoved <<
" shortcuts(s)";
163 if (allOwners && allKeys && allIds) {
164 itemsRemoved = d->shortcuts.size();
165 d->shortcuts.clear();
169 int i = d->shortcuts.size()-1;
172 const QShortcutEntry &entry = d->shortcuts.at(i);
173 int entryId = entry.id;
174 if ((allOwners || entry.owner == owner)
175 && (allIds || entry.id == id)
176 && (allKeys || entry.keySequence == keySequence)) {
177 d->shortcuts.removeAt(i);
188
189
190
191
192
193
194
195int QShortcutMap::setShortcutEnabled(
bool enable,
int id, QObject *owner,
const QKeySequence &keySequence)
198 int itemsChanged = 0;
199 bool allOwners = (owner ==
nullptr);
200 bool allKeys = keySequence.isEmpty();
201 bool allIds = id == 0;
203 int i = d->shortcuts.size()-1;
206 const QShortcutEntry &entry = d->shortcuts.at(i);
207 if ((allOwners || entry.owner == owner)
208 && (allIds || entry.id == id)
209 && (allKeys || entry.keySequence == keySequence)) {
210 d->shortcuts[i].enabled = enable;
217 qCDebug(lcShortcutMap).nospace()
218 <<
"QShortcutMap::setShortcutEnabled(" << enable <<
", " << id <<
", "
219 << owner <<
", " << keySequence <<
") = " << itemsChanged;
224
225
226
227
228
229
230
231int QShortcutMap::setShortcutAutoRepeat(
bool on,
int id, QObject *owner,
const QKeySequence &keySequence)
234 int itemsChanged = 0;
235 bool allOwners = (owner ==
nullptr);
236 bool allKeys = keySequence.isEmpty();
237 bool allIds = id == 0;
239 int i = d->shortcuts.size()-1;
242 QShortcutEntry entry = d->shortcuts.at(i);
243 if ((allOwners || entry.owner == owner)
244 && (allIds || entry.id == id)
245 && (allKeys || entry.keySequence == keySequence)) {
246 d->shortcuts[i].autorepeat = on;
253 qCDebug(lcShortcutMap).nospace()
254 <<
"QShortcutMap::setShortcutAutoRepeat(" << on <<
", " << id <<
", "
255 << owner <<
", " << keySequence <<
") = " << itemsChanged;
260
261
262void QShortcutMap::resetState()
265 d->currentState = QKeySequence::NoMatch;
266 clearSequence(d->currentSequences);
270
271
272QKeySequence::SequenceMatch QShortcutMap::state()
275 return d->currentState;
279
280
281
282
283
284
285
286
287bool QShortcutMap::tryShortcut(QKeyEvent *e)
291 if (e->key() == Qt::Key_unknown)
294 QKeySequence::SequenceMatch previousState = state();
296 switch (nextState(e)) {
297 case QKeySequence::NoMatch:
301 return previousState == QKeySequence::PartialMatch;
302 case QKeySequence::PartialMatch:
306 case QKeySequence::ExactMatch: {
309 const int identicalMatches = d->identicals.size();
314 return identicalMatches > 0;
317 Q_UNREACHABLE_RETURN(
false);
321
322
323
324
325
326QKeySequence::SequenceMatch QShortcutMap::nextState(QKeyEvent *e)
330 if (e->key() >= Qt::Key_Shift &&
331 e->key() <= Qt::Key_ScrollLock)
332 return d->currentState;
334 QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
337 d->identicals.clear();
340 if (result == QKeySequence::NoMatch && (e->modifiers() & Qt::KeypadModifier)) {
342 result = find(e, Qt::KeypadModifier);
344 if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) {
346 if (e->key() == Qt::Key_Backtab) {
347 QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text());
353 if (result == QKeySequence::NoMatch)
354 clearSequence(d->currentSequences);
355 d->currentState = result;
357 qCDebug(lcShortcutMap).nospace() <<
"QShortcutMap::nextState(" << e <<
") = " << result;
363
364
365bool QShortcutMap::hasShortcutForKeySequence(
const QKeySequence &seq)
const
367 Q_D(
const QShortcutMap);
368 QShortcutEntry entry(seq);
369 const auto itEnd = d->shortcuts.cend();
370 auto it = std::lower_bound(d->shortcuts.cbegin(), itEnd, entry);
372 for (;it != itEnd; ++it) {
373 if (entry.keySequence.matches(it->keySequence) == QKeySequence::ExactMatch
374 && (*it).correctContext() && (*it).enabled) {
384
385
386
387
388
389
390QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e,
int ignoredModifiers)
393 if (!d->shortcuts.size())
394 return QKeySequence::NoMatch;
396 createNewSequences(e, d->newEntries, ignoredModifiers);
397 qCDebug(lcShortcutMap) <<
"Possible input sequences:" << d->newEntries;
400 if (d->newEntries == d->currentSequences) {
401 Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().size(),
402 "QShortcutMap::find",
"New sequence to find identical to previous");
403 return QKeySequence::NoMatch;
407 d->identicals.clear();
409 bool partialFound =
false;
410 bool identicalDisabledFound =
false;
411 QList<QKeySequence> okEntries;
412 QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
413 for (
int i = d->newEntries.size()-1; i >= 0 ; --i) {
414 QShortcutEntry entry(d->newEntries.at(i));
415 qCDebug(lcShortcutMap) <<
"Looking for shortcuts matching" << entry.keySequence;
417 QKeySequence::SequenceMatch bestMatchForEntry = QKeySequence::NoMatch;
419 const auto itEnd = d->shortcuts.constEnd();
420 auto it = std::lower_bound(d->shortcuts.constBegin(), itEnd, entry);
421 for (; it != itEnd; ++it) {
422 QKeySequence::SequenceMatch match = entry.keySequence.matches(it->keySequence);
423 qCDebug(lcShortcutMap) <<
" -" << match <<
"for shortcut" << it->keySequence;
427 if (match == QKeySequence::NoMatch)
430 bestMatchForEntry = qMax(bestMatchForEntry, match);
432 if ((*it).correctContext()) {
433 if (match == QKeySequence::ExactMatch) {
435 d->identicals.append(&*it);
437 identicalDisabledFound =
true;
438 }
else if (match == QKeySequence::PartialMatch) {
440 if (d->identicals.size())
444 partialFound |= (*it).enabled;
447 qCDebug(lcShortcutMap) <<
" - But context was not correct";
453 if (bestMatchForEntry > result) {
455 qCDebug(lcShortcutMap) <<
"Found better match (" << d->newEntries <<
"), clearing key sequence list";
457 if (bestMatchForEntry && bestMatchForEntry >= result) {
458 okEntries << d->newEntries.at(i);
459 qCDebug(lcShortcutMap) <<
"Added ok key sequence" << d->newEntries;
463 if (d->identicals.size()) {
464 result = QKeySequence::ExactMatch;
465 }
else if (partialFound) {
466 result = QKeySequence::PartialMatch;
467 }
else if (identicalDisabledFound) {
468 result = QKeySequence::ExactMatch;
470 clearSequence(d->currentSequences);
471 result = QKeySequence::NoMatch;
473 if (result != QKeySequence::NoMatch)
474 d->currentSequences = okEntries;
475 qCDebug(lcShortcutMap) <<
"Returning shortcut match == " << result;
480
481
482
483
484void QShortcutMap::clearSequence(QList<QKeySequence> &ksl)
487 d_func()->newEntries.clear();
491
492
493
494void QShortcutMap::createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl,
int ignoredModifiers)
497 QList<QKeyCombination> possibleKeys = QKeyMapper::possibleKeys(e);
498 qCDebug(lcShortcutMap) <<
"Creating new sequences for" << e
499 <<
"with ignoredModifiers=" << Qt::KeyboardModifiers(ignoredModifiers);
500 int pkTotal = possibleKeys.size();
504 int ssActual = d->currentSequences.size();
505 int ssTotal = qMax(1, ssActual);
507 ksl.resize(pkTotal * ssTotal);
509 int index = ssActual ? d->currentSequences.at(0).count() : 0;
510 for (
int pkNum = 0; pkNum < pkTotal; ++pkNum) {
511 for (
int ssNum = 0; ssNum < ssTotal; ++ssNum) {
512 int i = (pkNum * ssTotal) + ssNum;
513 QKeySequence &curKsl = ksl[i];
515 const QKeySequence &curSeq = d->currentSequences.at(ssNum);
516 curKsl.setKey(curSeq[0], 0);
517 curKsl.setKey(curSeq[1], 1);
518 curKsl.setKey(curSeq[2], 2);
519 curKsl.setKey(curSeq[3], 3);
521 curKsl.setKey(QKeyCombination::fromCombined(0), 0);
522 curKsl.setKey(QKeyCombination::fromCombined(0), 1);
523 curKsl.setKey(QKeyCombination::fromCombined(0), 2);
524 curKsl.setKey(QKeyCombination::fromCombined(0), 3);
526 const int key = possibleKeys.at(pkNum).toCombined();
527 curKsl.setKey(QKeyCombination::fromCombined(key & ~ignoredModifiers), index);
533
534
535int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers)
538 if (modifiers & Qt::ShiftModifier)
540 if (modifiers & Qt::ControlModifier)
542 if (modifiers & Qt::MetaModifier)
544 if (modifiers & Qt::AltModifier)
550
551
552QList<
const QShortcutEntry*> QShortcutMap::matches()
const
554 Q_D(
const QShortcutMap);
555 return d->identicals;
559
560
561void QShortcutMap::dispatchEvent(QKeyEvent *e)
564 if (!d->identicals.size())
567 const QKeySequence &curKey = d->identicals.at(0)->keySequence;
568 if (d->prevSequence != curKey) {
570 d->prevSequence = curKey;
573 const QShortcutEntry *current =
nullptr, *next =
nullptr;
574 int i = 0, enabledShortcuts = 0;
575 QList<
const QShortcutEntry*> ambiguousShortcuts;
576 while(i < d->identicals.size()) {
577 current = d->identicals.at(i);
578 if (current->enabled || !next){
580 if (lcShortcutMap().isDebugEnabled())
581 ambiguousShortcuts.append(current);
582 if (enabledShortcuts > d->ambigCount + 1)
588 d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1);
591 if (!next || (e->isAutoRepeat() && !next->autorepeat))
594 if (lcShortcutMap().isDebugEnabled()) {
595 if (ambiguousShortcuts.size() > 1) {
596 qCDebug(lcShortcutMap) <<
"The following shortcuts are about to be activated ambiguously:";
597 for (
const QShortcutEntry *entry : std::as_const(ambiguousShortcuts))
598 qCDebug(lcShortcutMap).nospace() <<
"- " << entry->keySequence <<
" (belonging to " << entry->owner <<
")";
601 qCDebug(lcShortcutMap).nospace()
602 <<
"QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\""
603 << next->keySequence.toString() <<
"\", " << next->id <<
", "
604 <<
static_cast<
bool>(enabledShortcuts>1) <<
") to object(" << next->owner <<
')';
606 QShortcutEvent se(next->keySequence, next->id, enabledShortcuts > 1);
607 QCoreApplication::sendEvent(
const_cast<QObject *>(next->owner), &se);
610QList<QKeySequence> QShortcutMap::keySequences(
bool getAll)
const
612 Q_D(
const QShortcutMap);
613 QList<QKeySequence> keys;
614 for (
auto sequence : d->shortcuts) {
615 bool addSequence =
false;
616 if (sequence.enabled) {
617 if (getAll || sequence.context == Qt::ApplicationShortcut ||
618 sequence.owner == QGuiApplication::focusObject()) {
621 QObject *possibleWindow = sequence.owner;
622 while (possibleWindow) {
623 if (qobject_cast<QWindow *>(possibleWindow))
625 possibleWindow = possibleWindow->parent();
627 if (possibleWindow == QGuiApplication::focusWindow()) {
628 if (sequence.context == Qt::WindowShortcut) {
630 }
else if (sequence.context == Qt::WidgetWithChildrenShortcut) {
631 QObject *possibleWidget = QGuiApplication::focusObject();
632 while (possibleWidget->parent()) {
633 possibleWidget = possibleWidget->parent();
634 if (possibleWidget == sequence.owner) {
643 keys << sequence.keySequence;
651
652
653
654#if defined(Dump_QShortcutMap)
655void QShortcutMap::dumpMap()
const
657 Q_D(
const QShortcutMap);
658 for (
int i = 0; i < d->shortcuts.size(); ++i)
659 qDebug().nospace() << &(d->shortcuts.at(i));
QList< const QShortcutEntry * > identicals
QKeySequence prevSequence
QList< QKeySequence > newEntries
QList< QShortcutEntry > shortcuts
QList< QKeySequence > currentSequences
QKeySequence::SequenceMatch currentState
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
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