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