1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qshortcutmap_p.h"
41 #include "private/qobject_p.h"
42 #include "qkeysequence.h"
43 #include "qdebug.h"
44 #include "qevent.h"
45 #include "qvector.h"
46 #include "qcoreapplication.h"
47 #include <private/qkeymapper_p.h>
48 #include <QtCore/qloggingcategory.h>
49 
50 #include <algorithm>
51 
52 #ifndef QT_NO_SHORTCUT
53 
54 QT_BEGIN_NAMESPACE
55 
56 Q_LOGGING_CATEGORY(lcShortcutMap, "qt.gui.shortcutmap")
57 
58 /* \internal
59     Entry data for QShortcutMap
60     Contains:
61         Keysequence for entry
62         Pointer to parent owning the sequence
63 */
64 
65 struct QShortcutEntry
66 {
QShortcutEntryQShortcutEntry67     QShortcutEntry()
68         : keyseq(0), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(nullptr), contextMatcher(nullptr)
69     {}
70 
QShortcutEntryQShortcutEntry71     QShortcutEntry(const QKeySequence &k)
72         : keyseq(k), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(nullptr), contextMatcher(nullptr)
73     {}
74 
QShortcutEntryQShortcutEntry75     QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i, bool a, QShortcutMap::ContextMatcher m)
76         : keyseq(k), context(c), enabled(true), autorepeat(a), id(i), owner(o), contextMatcher(m)
77     {}
78 
correctContextQShortcutEntry79     bool correctContext() const { return contextMatcher(owner, context); }
80 
operator <QShortcutEntry81     bool operator<(const QShortcutEntry &f) const
82     { return keyseq < f.keyseq; }
83 
84     QKeySequence keyseq;
85     Qt::ShortcutContext context;
86     bool enabled : 1;
87     bool autorepeat : 1;
88     signed int id;
89     QObject *owner;
90     QShortcutMap::ContextMatcher contextMatcher;
91 };
92 Q_DECLARE_TYPEINFO(QShortcutEntry, Q_MOVABLE_TYPE);
93 
94 #ifdef Dump_QShortcutMap
95 /*! \internal
96     QDebug operator<< for easy debug output of the shortcut entries.
97 */
operator <<(QDebug & dbg,const QShortcutEntry * se)98 static QDebug &operator<<(QDebug &dbg, const QShortcutEntry *se)
99 {
100     QDebugStateSaver saver(dbg);
101     if (!se)
102         return dbg << "QShortcutEntry(0x0)";
103     dbg.nospace()
104         << "QShortcutEntry(" << se->keyseq
105         << "), id(" << se->id << "), enabled(" << se->enabled << "), autorepeat(" << se->autorepeat
106         << "), owner(" << se->owner << ')';
107     return dbg;
108 }
109 #endif // Dump_QShortcutMap
110 
111 /* \internal
112     Private data for QShortcutMap
113 */
114 class QShortcutMapPrivate
115 {
116     Q_DECLARE_PUBLIC(QShortcutMap)
117 
118 public:
QShortcutMapPrivate(QShortcutMap * parent)119     QShortcutMapPrivate(QShortcutMap* parent)
120         : q_ptr(parent), currentId(0), ambigCount(0), currentState(QKeySequence::NoMatch)
121     {
122         identicals.reserve(10);
123         currentSequences.reserve(10);
124     }
125     QShortcutMap *q_ptr;                        // Private's parent
126 
127     QVector<QShortcutEntry> sequences;          // All sequences!
128 
129     int currentId;                              // Global shortcut ID number
130     int ambigCount;                             // Index of last enabled ambiguous dispatch
131     QKeySequence::SequenceMatch currentState;
132     QVector<QKeySequence> currentSequences;     // Sequence for the current state
133     QVector<QKeySequence> newEntries;
134     QKeySequence prevSequence;                  // Sequence for the previous identical match
135     QVector<const QShortcutEntry*> identicals;  // Last identical matches
136 };
137 
138 
139 /*! \internal
140     QShortcutMap constructor.
141 */
QShortcutMap()142 QShortcutMap::QShortcutMap()
143     : d_ptr(new QShortcutMapPrivate(this))
144 {
145     resetState();
146 }
147 
148 /*! \internal
149     QShortcutMap destructor.
150 */
~QShortcutMap()151 QShortcutMap::~QShortcutMap()
152 {
153 }
154 
155 /*! \internal
156     Adds a shortcut to the global map.
157     Returns the id of the newly added shortcut.
158 */
addShortcut(QObject * owner,const QKeySequence & key,Qt::ShortcutContext context,ContextMatcher matcher)159 int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &key, Qt::ShortcutContext context, ContextMatcher matcher)
160 {
161     Q_ASSERT_X(owner, "QShortcutMap::addShortcut", "All shortcuts need an owner");
162     Q_ASSERT_X(!key.isEmpty(), "QShortcutMap::addShortcut", "Cannot add keyless shortcuts to map");
163     Q_D(QShortcutMap);
164 
165     QShortcutEntry newEntry(owner, key, context, --(d->currentId), true, matcher);
166     const auto it = std::upper_bound(d->sequences.begin(), d->sequences.end(), newEntry);
167     d->sequences.insert(it, newEntry); // Insert sorted
168     qCDebug(lcShortcutMap).nospace()
169         << "QShortcutMap::addShortcut(" << owner << ", "
170         << key << ", " << context << ") = " << d->currentId;
171     return d->currentId;
172 }
173 
174 /*! \internal
175     Removes a shortcut from the global map.
176     If \a owner is \nullptr, all entries in the map with the key sequence specified
177     is removed. If \a key is null, all sequences for \a owner is removed from
178     the map. If \a id is 0, any identical \a key sequences owned by \a owner
179     are removed.
180     Returns the number of sequences removed from the map.
181 */
182 
removeShortcut(int id,QObject * owner,const QKeySequence & key)183 int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key)
184 {
185     Q_D(QShortcutMap);
186     int itemsRemoved = 0;
187     bool allOwners = (owner == nullptr);
188     bool allKeys = key.isEmpty();
189     bool allIds = id == 0;
190 
191     // Special case, remove everything
192     if (allOwners && allKeys && allIds) {
193         itemsRemoved = d->sequences.size();
194         d->sequences.clear();
195         return itemsRemoved;
196     }
197 
198     int i = d->sequences.size()-1;
199     while (i>=0)
200     {
201         const QShortcutEntry &entry = d->sequences.at(i);
202         int entryId = entry.id;
203         if ((allOwners || entry.owner == owner)
204             && (allIds || entry.id == id)
205             && (allKeys || entry.keyseq == key)) {
206             d->sequences.removeAt(i);
207             ++itemsRemoved;
208         }
209         if (id == entryId)
210             return itemsRemoved;
211         --i;
212     }
213     qCDebug(lcShortcutMap).nospace()
214         << "QShortcutMap::removeShortcut(" << id << ", " << owner << ", "
215         << key << ") = " << itemsRemoved;
216     return itemsRemoved;
217 }
218 
219 /*! \internal
220     Changes the enable state of a shortcut to \a enable.
221     If \a owner is \nullptr, all entries in the map with the key sequence specified
222     is removed. If \a key is null, all sequences for \a owner is removed from
223     the map. If \a id is 0, any identical \a key sequences owned by \a owner
224     are changed.
225     Returns the number of sequences which are matched in the map.
226 */
setShortcutEnabled(bool enable,int id,QObject * owner,const QKeySequence & key)227 int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &key)
228 {
229     Q_D(QShortcutMap);
230     int itemsChanged = 0;
231     bool allOwners = (owner == nullptr);
232     bool allKeys = key.isEmpty();
233     bool allIds = id == 0;
234 
235     int i = d->sequences.size()-1;
236     while (i>=0)
237     {
238         QShortcutEntry entry = d->sequences.at(i);
239         if ((allOwners || entry.owner == owner)
240             && (allIds || entry.id == id)
241             && (allKeys || entry.keyseq == key)) {
242             d->sequences[i].enabled = enable;
243             ++itemsChanged;
244         }
245         if (id == entry.id)
246             return itemsChanged;
247         --i;
248     }
249     qCDebug(lcShortcutMap).nospace()
250         << "QShortcutMap::setShortcutEnabled(" << enable << ", " << id << ", "
251         << owner << ", " << key << ") = " << itemsChanged;
252     return itemsChanged;
253 }
254 
255 /*! \internal
256     Changes the auto repeat state of a shortcut to \a enable.
257     If \a owner is \nullptr, all entries in the map with the key sequence specified
258     is removed. If \a key is null, all sequences for \a owner is removed from
259     the map. If \a id is 0, any identical \a key sequences owned by \a owner
260     are changed.
261     Returns the number of sequences which are matched in the map.
262 */
setShortcutAutoRepeat(bool on,int id,QObject * owner,const QKeySequence & key)263 int QShortcutMap::setShortcutAutoRepeat(bool on, int id, QObject *owner, const QKeySequence &key)
264 {
265     Q_D(QShortcutMap);
266     int itemsChanged = 0;
267     bool allOwners = (owner == nullptr);
268     bool allKeys = key.isEmpty();
269     bool allIds = id == 0;
270 
271     int i = d->sequences.size()-1;
272     while (i>=0)
273     {
274         QShortcutEntry entry = d->sequences.at(i);
275         if ((allOwners || entry.owner == owner)
276             && (allIds || entry.id == id)
277             && (allKeys || entry.keyseq == key)) {
278                 d->sequences[i].autorepeat = on;
279                 ++itemsChanged;
280         }
281         if (id == entry.id)
282             return itemsChanged;
283         --i;
284     }
285     qCDebug(lcShortcutMap).nospace()
286         << "QShortcutMap::setShortcutAutoRepeat(" << on << ", " << id << ", "
287         << owner << ", " << key << ") = " << itemsChanged;
288     return itemsChanged;
289 }
290 
291 /*! \internal
292     Resets the state of the statemachine to NoMatch
293 */
resetState()294 void QShortcutMap::resetState()
295 {
296     Q_D(QShortcutMap);
297     d->currentState = QKeySequence::NoMatch;
298     clearSequence(d->currentSequences);
299 }
300 
301 /*! \internal
302     Returns the current state of the statemachine
303 */
state()304 QKeySequence::SequenceMatch QShortcutMap::state()
305 {
306     Q_D(QShortcutMap);
307     return d->currentState;
308 }
309 
310 /*! \internal
311     Uses nextState(QKeyEvent) to check for a grabbed shortcut.
312 
313     If so, it is dispatched using dispatchEvent().
314 
315     Returns true if a shortcut handled the event.
316 
317     \sa nextState, dispatchEvent
318 */
tryShortcut(QKeyEvent * e)319 bool QShortcutMap::tryShortcut(QKeyEvent *e)
320 {
321     Q_D(QShortcutMap);
322 
323     if (e->key() == Qt::Key_unknown)
324         return false;
325 
326     QKeySequence::SequenceMatch previousState = state();
327 
328     switch (nextState(e)) {
329     case QKeySequence::NoMatch:
330         // In the case of going from a partial match to no match we handled the
331         // event, since we already stated that we did for the partial match. But
332         // in the normal case of directly going to no match we say we didn't.
333         return previousState == QKeySequence::PartialMatch;
334     case QKeySequence::PartialMatch:
335         // For a partial match we don't know yet if we will handle the shortcut
336         // but we need to say we did, so that we get the follow-up key-presses.
337         return true;
338     case QKeySequence::ExactMatch: {
339         // Save number of identical matches before dispatching
340         // to keep QShortcutMap and tryShortcut reentrant.
341         const int identicalMatches = d->identicals.count();
342         resetState();
343         dispatchEvent(e);
344         // If there are no identicals we've only found disabled shortcuts, and
345         // shouldn't say that we handled the event.
346         return identicalMatches > 0;
347     }
348     }
349     Q_UNREACHABLE();
350     return false;
351 }
352 
353 /*! \internal
354     Returns the next state of the statemachine
355     If return value is SequenceMatch::ExactMatch, then a call to matches()
356     will return a QObjects* list of all matching objects for the last matching
357     sequence.
358 */
nextState(QKeyEvent * e)359 QKeySequence::SequenceMatch QShortcutMap::nextState(QKeyEvent *e)
360 {
361     Q_D(QShortcutMap);
362     // Modifiers can NOT be shortcuts...
363     if (e->key() >= Qt::Key_Shift &&
364         e->key() <= Qt::Key_Alt)
365         return d->currentState;
366 
367     QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
368 
369     // We start fresh each time..
370     d->identicals.clear();
371 
372     result = find(e);
373     if (result == QKeySequence::NoMatch && (e->modifiers() & Qt::KeypadModifier)) {
374         // Try to find a match without keypad modifier
375         result = find(e, Qt::KeypadModifier);
376     }
377     if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) {
378         // If Shift + Key_Backtab, also try Shift + Qt::Key_Tab
379         if (e->key() == Qt::Key_Backtab) {
380             QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text());
381             result = find(&pe);
382         }
383     }
384 
385     // Does the new state require us to clean up?
386     if (result == QKeySequence::NoMatch)
387         clearSequence(d->currentSequences);
388     d->currentState = result;
389 
390     qCDebug(lcShortcutMap).nospace() << "QShortcutMap::nextState(" << e << ") = " << result;
391     return result;
392 }
393 
394 
395 /*! \internal
396     Determines if an enabled shortcut has a matcing key sequence.
397 */
hasShortcutForKeySequence(const QKeySequence & seq) const398 bool QShortcutMap::hasShortcutForKeySequence(const QKeySequence &seq) const
399 {
400     Q_D(const QShortcutMap);
401     QShortcutEntry entry(seq); // needed for searching
402     const auto itEnd = d->sequences.cend();
403     auto it = std::lower_bound(d->sequences.cbegin(), itEnd, entry);
404 
405     for (;it != itEnd; ++it) {
406         if (matches(entry.keyseq, (*it).keyseq) == QKeySequence::ExactMatch && (*it).correctContext() && (*it).enabled) {
407             return true;
408         }
409     }
410 
411     //end of the loop: we didn't find anything
412     return false;
413 }
414 
415 /*! \internal
416     Returns the next state of the statemachine, based
417     on the new key event \a e.
418     Matches are appended to the vector of identicals,
419     which can be access through matches().
420     \sa matches
421 */
find(QKeyEvent * e,int ignoredModifiers)422 QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifiers)
423 {
424     Q_D(QShortcutMap);
425     if (!d->sequences.count())
426         return QKeySequence::NoMatch;
427 
428     createNewSequences(e, d->newEntries, ignoredModifiers);
429     qCDebug(lcShortcutMap) << "Possible shortcut key sequences:" << d->newEntries;
430 
431     // Should never happen
432     if (d->newEntries == d->currentSequences) {
433         Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().length(),
434                    "QShortcutMap::find", "New sequence to find identical to previous");
435         return QKeySequence::NoMatch;
436     }
437 
438     // Looking for new identicals, scrap old
439     d->identicals.clear();
440 
441     bool partialFound = false;
442     bool identicalDisabledFound = false;
443     QVector<QKeySequence> okEntries;
444     int result = QKeySequence::NoMatch;
445     for (int i = d->newEntries.count()-1; i >= 0 ; --i) {
446         QShortcutEntry entry(d->newEntries.at(i)); // needed for searching
447         const auto itEnd = d->sequences.constEnd();
448         auto it = std::lower_bound(d->sequences.constBegin(), itEnd, entry);
449 
450         int oneKSResult = QKeySequence::NoMatch;
451         int tempRes = QKeySequence::NoMatch;
452         do {
453             if (it == itEnd)
454                 break;
455             tempRes = matches(entry.keyseq, (*it).keyseq);
456             oneKSResult = qMax(oneKSResult, tempRes);
457             if (tempRes != QKeySequence::NoMatch && (*it).correctContext()) {
458                 if (tempRes == QKeySequence::ExactMatch) {
459                     if ((*it).enabled)
460                         d->identicals.append(&*it);
461                     else
462                         identicalDisabledFound = true;
463                 } else if (tempRes == QKeySequence::PartialMatch) {
464                     // We don't need partials, if we have identicals
465                     if (d->identicals.size())
466                         break;
467                     // We only care about enabled partials, so we don't consume
468                     // key events when all partials are disabled!
469                     partialFound |= (*it).enabled;
470                 }
471             }
472             ++it;
473             // If we got a valid match on this run, there might still be more keys to check against,
474             // so we'll loop once more. If we get NoMatch, there's guaranteed no more possible
475             // matches in the shortcutmap.
476         } while (tempRes != QKeySequence::NoMatch);
477 
478         // If the type of match improves (ergo, NoMatch->Partial, or Partial->Exact), clear the
479         // previous list. If this match is equal or better than the last match, append to the list
480         if (oneKSResult > result) {
481             okEntries.clear();
482             qCDebug(lcShortcutMap) << "Found better match (" << d->newEntries << "), clearing key sequence list";
483         }
484         if (oneKSResult && oneKSResult >= result) {
485             okEntries << d->newEntries.at(i);
486             qCDebug(lcShortcutMap) << "Added ok key sequence" << d->newEntries;
487         }
488     }
489 
490     if (d->identicals.size()) {
491         result = QKeySequence::ExactMatch;
492     } else if (partialFound) {
493         result = QKeySequence::PartialMatch;
494     } else if (identicalDisabledFound) {
495         result = QKeySequence::ExactMatch;
496     } else {
497         clearSequence(d->currentSequences);
498         result = QKeySequence::NoMatch;
499     }
500     if (result != QKeySequence::NoMatch)
501         d->currentSequences = okEntries;
502     qCDebug(lcShortcutMap) << "Returning shortcut match == " << result;
503     return QKeySequence::SequenceMatch(result);
504 }
505 
506 /*! \internal
507     Clears \a seq to an empty QKeySequence.
508     Same as doing (the slower)
509     \snippet code/src_gui_kernel_qshortcutmap.cpp 0
510 */
clearSequence(QVector<QKeySequence> & ksl)511 void QShortcutMap::clearSequence(QVector<QKeySequence> &ksl)
512 {
513     ksl.clear();
514     d_func()->newEntries.clear();
515 }
516 
517 /*! \internal
518     Alters \a seq to the new sequence state, based on the
519     current sequence state, and the new key event \a e.
520 */
createNewSequences(QKeyEvent * e,QVector<QKeySequence> & ksl,int ignoredModifiers)521 void QShortcutMap::createNewSequences(QKeyEvent *e, QVector<QKeySequence> &ksl, int ignoredModifiers)
522 {
523     Q_D(QShortcutMap);
524     QList<int> possibleKeys = QKeyMapper::possibleKeys(e);
525     if (lcShortcutMap().isDebugEnabled()) {
526         qCDebug(lcShortcutMap).nospace() << __FUNCTION__ << '(' << e << ", ignoredModifiers="
527             << Qt::KeyboardModifiers(ignoredModifiers) << "), possibleKeys=(";
528         for (int i = 0, size = possibleKeys.size(); i < size; ++i) {
529             if (i)
530                 qCDebug(lcShortcutMap).nospace() << ", ";
531             qCDebug(lcShortcutMap).nospace() << QKeySequence(possibleKeys.at(i));
532         }
533         qCDebug(lcShortcutMap).nospace() << ')';
534     }
535     int pkTotal = possibleKeys.count();
536     if (!pkTotal)
537         return;
538 
539     int ssActual = d->currentSequences.count();
540     int ssTotal = qMax(1, ssActual);
541     // Resize to possible permutations of the current sequence(s).
542     ksl.resize(pkTotal * ssTotal);
543 
544     int index = ssActual ? d->currentSequences.at(0).count() : 0;
545     for (int pkNum = 0; pkNum < pkTotal; ++pkNum) {
546         for (int ssNum = 0; ssNum < ssTotal; ++ssNum) {
547             int i = (pkNum * ssTotal) + ssNum;
548             QKeySequence &curKsl = ksl[i];
549             if (ssActual) {
550                 const QKeySequence &curSeq = d->currentSequences.at(ssNum);
551                 curKsl.setKey(curSeq[0], 0);
552                 curKsl.setKey(curSeq[1], 1);
553                 curKsl.setKey(curSeq[2], 2);
554                 curKsl.setKey(curSeq[3], 3);
555             } else {
556                 curKsl.setKey(0, 0);
557                 curKsl.setKey(0, 1);
558                 curKsl.setKey(0, 2);
559                 curKsl.setKey(0, 3);
560             }
561             curKsl.setKey(possibleKeys.at(pkNum) & ~ignoredModifiers, index);
562         }
563     }
564 }
565 
566 /*! \internal
567     Basically the same function as QKeySequence::matches(const QKeySequence &seq) const
568     only that is specially handles Key_hyphen as Key_Minus, as people mix these up all the time and
569     they conceptually the same.
570 */
matches(const QKeySequence & seq1,const QKeySequence & seq2) const571 QKeySequence::SequenceMatch QShortcutMap::matches(const QKeySequence &seq1,
572                                                   const QKeySequence &seq2) const
573 {
574     uint userN = seq1.count(),
575         seqN = seq2.count();
576 
577     if (userN > seqN)
578         return QKeySequence::NoMatch;
579 
580     // If equal in length, we have a potential ExactMatch sequence,
581     // else we already know it can only be partial.
582     QKeySequence::SequenceMatch match = (userN == seqN
583                                             ? QKeySequence::ExactMatch
584                                             : QKeySequence::PartialMatch);
585 
586     for (uint i = 0; i < userN; ++i) {
587         int userKey = seq1[i],
588             sequenceKey = seq2[i];
589         if ((userKey & Qt::Key_unknown) == Qt::Key_hyphen)
590             userKey = (userKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
591         if ((sequenceKey & Qt::Key_unknown) == Qt::Key_hyphen)
592             sequenceKey = (sequenceKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
593         if (userKey != sequenceKey)
594             return QKeySequence::NoMatch;
595     }
596     return match;
597 }
598 
599 
600 /*! \internal
601     Converts keyboard button states into modifier states
602 */
translateModifiers(Qt::KeyboardModifiers modifiers)603 int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers)
604 {
605     int result = 0;
606     if (modifiers & Qt::ShiftModifier)
607         result |= Qt::SHIFT;
608     if (modifiers & Qt::ControlModifier)
609         result |= Qt::CTRL;
610     if (modifiers & Qt::MetaModifier)
611         result |= Qt::META;
612     if (modifiers & Qt::AltModifier)
613         result |= Qt::ALT;
614     return result;
615 }
616 
617 /*! \internal
618     Returns the vector of QShortcutEntry's matching the last Identical state.
619 */
matches() const620 QVector<const QShortcutEntry*> QShortcutMap::matches() const
621 {
622     Q_D(const QShortcutMap);
623     return d->identicals;
624 }
625 
626 /*! \internal
627     Dispatches QShortcutEvents to widgets who grabbed the matched key sequence.
628 */
dispatchEvent(QKeyEvent * e)629 void QShortcutMap::dispatchEvent(QKeyEvent *e)
630 {
631     Q_D(QShortcutMap);
632     if (!d->identicals.size())
633         return;
634 
635     const QKeySequence &curKey = d->identicals.at(0)->keyseq;
636     if (d->prevSequence != curKey) {
637         d->ambigCount = 0;
638         d->prevSequence = curKey;
639     }
640     // Find next
641     const QShortcutEntry *current = nullptr, *next = nullptr;
642     int i = 0, enabledShortcuts = 0;
643     QVector<const QShortcutEntry*> ambiguousShortcuts;
644     while(i < d->identicals.size()) {
645         current = d->identicals.at(i);
646         if (current->enabled || !next){
647             ++enabledShortcuts;
648             if (lcShortcutMap().isDebugEnabled())
649                 ambiguousShortcuts.append(current);
650             if (enabledShortcuts > d->ambigCount + 1)
651                 break;
652             next = current;
653         }
654         ++i;
655     }
656     d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1);
657     // Don't trigger shortcut if we're autorepeating and the shortcut is
658     // grabbed with not accepting autorepeats.
659     if (!next || (e->isAutoRepeat() && !next->autorepeat))
660         return;
661     // Dispatch next enabled
662     if (lcShortcutMap().isDebugEnabled()) {
663         if (ambiguousShortcuts.size() > 1) {
664             qCDebug(lcShortcutMap) << "The following shortcuts are about to be activated ambiguously:";
665             for (const QShortcutEntry *entry : qAsConst(ambiguousShortcuts))
666                 qCDebug(lcShortcutMap).nospace() << "- " << entry->keyseq << " (belonging to " << entry->owner << ")";
667         }
668 
669         qCDebug(lcShortcutMap).nospace()
670             << "QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\""
671             << next->keyseq.toString() << "\", " << next->id << ", "
672             << static_cast<bool>(enabledShortcuts>1) << ") to object(" << next->owner << ')';
673     }
674     QShortcutEvent se(next->keyseq, next->id, enabledShortcuts>1);
675     QCoreApplication::sendEvent(const_cast<QObject *>(next->owner), &se);
676 }
677 
678 /* \internal
679     QShortcutMap dump function, only available when DEBUG_QSHORTCUTMAP is
680     defined.
681 */
682 #if defined(Dump_QShortcutMap)
dumpMap() const683 void QShortcutMap::dumpMap() const
684 {
685     Q_D(const QShortcutMap);
686     for (int i = 0; i < d->sequences.size(); ++i)
687         qDebug().nospace() << &(d->sequences.at(i));
688 }
689 #endif
690 
691 QT_END_NAMESPACE
692 
693 #endif // QT_NO_SHORTCUT
694