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