source: trunk/src/gui/kernel/qshortcutmap.cpp@ 467

Last change on this file since 467 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 29.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qshortcutmap_p.h"
43#include "private/qobject_p.h"
44#include "qkeysequence.h"
45#include "qgraphicsscene.h"
46#include "qgraphicsview.h"
47#include "qdebug.h"
48#include "qevent.h"
49#include "qwidget.h"
50#include "qapplication.h"
51#include "qvector.h"
52#include "qmenu.h"
53#include "qmenubar.h"
54#include "qshortcut.h"
55#include "qapplication_p.h"
56#include <private/qaction_p.h>
57#include <private/qkeymapper_p.h>
58#include <private/qwidget_p.h>
59
60#ifndef QT_NO_SHORTCUT
61
62QT_BEGIN_NAMESPACE
63
64extern bool qt_mac_no_native_menubar; // qmenu_mac.cpp
65
66// To enable verbose output uncomment below
67//#define DEBUG_QSHORTCUTMAP
68
69/* \internal
70 Entry data for QShortcutMap
71 Contains:
72 Keysequence for entry
73 Pointer to parent owning the sequence
74*/
75struct QShortcutEntry
76{
77 QShortcutEntry()
78 : keyseq(0), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(0)
79 {}
80
81 QShortcutEntry(const QKeySequence &k)
82 : keyseq(k), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(0)
83 {}
84
85 QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i)
86 : keyseq(k), context(c), enabled(true), autorepeat(1), id(i), owner(o)
87 {}
88
89 QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i, bool a)
90 : keyseq(k), context(c), enabled(true), autorepeat(a), id(i), owner(o)
91 {}
92
93 bool operator<(const QShortcutEntry &f) const
94 { return keyseq < f.keyseq; }
95
96 QKeySequence keyseq;
97 Qt::ShortcutContext context;
98 bool enabled : 1;
99 bool autorepeat : 1;
100 signed int id;
101 QObject *owner;
102};
103
104#ifndef QT_NO_DEBUG_STREAM
105/*! \internal
106 QDebug operator<< for easy debug output of the shortcut entries.
107*/
108QDebug &operator<<(QDebug &dbg, const QShortcutEntry *se) {
109 if (!se)
110 return dbg << "QShortcutEntry(0x0)";
111 dbg.nospace()
112 << "QShortcutEntry(" << se->keyseq
113 << "), id(" << se->id << "), enabled(" << se->enabled << "), autorepeat(" << se->autorepeat
114 << "), owner(" << se->owner << ")";
115 return dbg.space();
116}
117#endif // QT_NO_DEBUGSTREAM
118
119/* \internal
120 Private data for QShortcutMap
121*/
122class QShortcutMapPrivate
123{
124 Q_DECLARE_PUBLIC(QShortcutMap)
125
126public:
127 QShortcutMapPrivate(QShortcutMap* parent)
128 : q_ptr(parent), currentId(0), ambigCount(0), currentState(QKeySequence::NoMatch)
129 {
130 identicals.reserve(10);
131 currentSequences.reserve(10);
132 }
133 QShortcutMap *q_ptr; // Private's parent
134
135 QList<QShortcutEntry> sequences; // All sequences!
136
137 int currentId; // Global shortcut ID number
138 int ambigCount; // Index of last enabled ambiguous dispatch
139 QKeySequence::SequenceMatch currentState;
140 QVector<QKeySequence> currentSequences; // Sequence for the current state
141 QVector<QKeySequence> newEntries;
142 QKeySequence prevSequence; // Sequence for the previous identical match
143 QVector<const QShortcutEntry*> identicals; // Last identical matches
144};
145
146
147/*! \internal
148 QShortcutMap constructor.
149*/
150QShortcutMap::QShortcutMap()
151{
152 d_ptr = new QShortcutMapPrivate(this);
153 Q_ASSERT(d_ptr != 0);
154 resetState();
155}
156
157/*! \internal
158 QShortcutMap destructor.
159*/
160QShortcutMap::~QShortcutMap()
161{
162 delete d_ptr;
163 d_ptr = 0;
164}
165
166/*! \internal
167 Adds a shortcut to the global map.
168 Returns the id of the newly added shortcut.
169*/
170int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &key, Qt::ShortcutContext context)
171{
172 Q_ASSERT_X(owner, "QShortcutMap::addShortcut", "All shortcuts need an owner");
173 Q_ASSERT_X(!key.isEmpty(), "QShortcutMap::addShortcut", "Cannot add keyless shortcuts to map");
174 Q_D(QShortcutMap);
175
176 QShortcutEntry newEntry(owner, key, context, --(d->currentId), true);
177 QList<QShortcutEntry>::iterator it = qUpperBound(d->sequences.begin(), d->sequences.end(), newEntry);
178 d->sequences.insert(it, newEntry); // Insert sorted
179#if defined(DEBUG_QSHORTCUTMAP)
180 qDebug().nospace()
181 << "QShortcutMap::addShortcut(" << owner << ", "
182 << key << ", " << context << ") = " << d->currentId;
183#endif
184 return d->currentId;
185}
186
187/*! \internal
188 Removes a shortcut from the global map.
189 If \a owner is 0, all entries in the map with the key sequence specified
190 is removed. If \a key is null, all sequences for \a owner is removed from
191 the map. If \a id is 0, any identical \a key sequences owned by \a owner
192 are removed.
193 Returns the number of sequences removed from the map.
194*/
195
196int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key)
197{
198 Q_D(QShortcutMap);
199 int itemsRemoved = 0;
200 bool allOwners = (owner == 0);
201 bool allKeys = key.isEmpty();
202 bool allIds = id == 0;
203
204 // Special case, remove everything
205 if (allOwners && allKeys && id == 0) {
206 itemsRemoved = d->sequences.size();
207 d->sequences.clear();
208 return itemsRemoved;
209 }
210
211 int i = d->sequences.size()-1;
212 while (i>=0)
213 {
214 const QShortcutEntry &entry = d->sequences.at(i);
215 int entryId = entry.id;
216 if ((allOwners || entry.owner == owner)
217 && (allIds || entry.id == id)
218 && (allKeys || entry.keyseq == key)) {
219 d->sequences.removeAt(i);
220 ++itemsRemoved;
221 }
222 if (id == entryId)
223 return itemsRemoved;
224 --i;
225 }
226#if defined(DEBUG_QSHORTCUTMAP)
227 qDebug().nospace()
228 << "QShortcutMap::removeShortcut(" << id << ", " << owner << ", "
229 << key << ") = " << itemsRemoved;
230#endif
231 return itemsRemoved;
232}
233
234/*! \internal
235 Changes the enable state of a shortcut to \a enable.
236 If \a owner is 0, all entries in the map with the key sequence specified
237 is removed. If \a key is null, all sequences for \a owner is removed from
238 the map. If \a id is 0, any identical \a key sequences owned by \a owner
239 are changed.
240 Returns the number of sequences which are matched in the map.
241*/
242int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &key)
243{
244 Q_D(QShortcutMap);
245 int itemsChanged = 0;
246 bool allOwners = (owner == 0);
247 bool allKeys = key.isEmpty();
248 bool allIds = id == 0;
249
250 int i = d->sequences.size()-1;
251 while (i>=0)
252 {
253 QShortcutEntry entry = d->sequences.at(i);
254 if ((allOwners || entry.owner == owner)
255 && (allIds || entry.id == id)
256 && (allKeys || entry.keyseq == key)) {
257 d->sequences[i].enabled = enable;
258 ++itemsChanged;
259 }
260 if (id == entry.id)
261 return itemsChanged;
262 --i;
263 }
264#if defined(DEBUG_QSHORTCUTMAP)
265 qDebug().nospace()
266 << "QShortcutMap::setShortcutEnabled(" << enable << ", " << id << ", "
267 << owner << ", " << key << ") = " << itemsChanged;
268#endif
269 return itemsChanged;
270}
271
272/*! \internal
273 Changes the auto repeat state of a shortcut to \a enable.
274 If \a owner is 0, all entries in the map with the key sequence specified
275 is removed. If \a key is null, all sequences for \a owner is removed from
276 the map. If \a id is 0, any identical \a key sequences owned by \a owner
277 are changed.
278 Returns the number of sequences which are matched in the map.
279*/
280int QShortcutMap::setShortcutAutoRepeat(bool on, int id, QObject *owner, const QKeySequence &key)
281{
282 Q_D(QShortcutMap);
283 int itemsChanged = 0;
284 bool allOwners = (owner == 0);
285 bool allKeys = key.isEmpty();
286 bool allIds = id == 0;
287
288 int i = d->sequences.size()-1;
289 while (i>=0)
290 {
291 QShortcutEntry entry = d->sequences.at(i);
292 if ((allOwners || entry.owner == owner)
293 && (allIds || entry.id == id)
294 && (allKeys || entry.keyseq == key)) {
295 d->sequences[i].autorepeat = on;
296 ++itemsChanged;
297 }
298 if (id == entry.id)
299 return itemsChanged;
300 --i;
301 }
302#if defined(DEBUG_QSHORTCUTMAP)
303 qDebug().nospace()
304 << "QShortcutMap::setShortcutAutoRepeat(" << on << ", " << id << ", "
305 << owner << ", " << key << ") = " << itemsChanged;
306#endif
307 return itemsChanged;
308}
309
310/*! \internal
311 Resets the state of the statemachine to NoMatch
312*/
313void QShortcutMap::resetState()
314{
315 Q_D(QShortcutMap);
316 d->currentState = QKeySequence::NoMatch;
317 clearSequence(d->currentSequences);
318}
319
320/*! \internal
321 Returns the current state of the statemachine
322*/
323QKeySequence::SequenceMatch QShortcutMap::state()
324{
325 Q_D(QShortcutMap);
326 return d->currentState;
327}
328
329/*! \internal
330 Uses ShortcutOverride event to see if any widgets want to override
331 the event. If not, uses nextState(QKeyEvent) to check for a grabbed
332 Shortcut, and dispatchEvent() is found an identical.
333 \sa nextState dispatchEvent
334*/
335bool QShortcutMap::tryShortcutEvent(QObject *o, QKeyEvent *e)
336{
337 Q_D(QShortcutMap);
338
339 bool wasAccepted = e->isAccepted();
340 bool wasSpontaneous = e->spont;
341 if (d->currentState == QKeySequence::NoMatch) {
342 ushort orgType = e->t;
343 e->t = QEvent::ShortcutOverride;
344 e->ignore();
345 QApplication::sendEvent(o, e);
346 e->t = orgType;
347 e->spont = wasSpontaneous;
348 if (e->isAccepted()) {
349 if (!wasAccepted)
350 e->ignore();
351 return false;
352 }
353 }
354
355 QKeySequence::SequenceMatch result = nextState(e);
356 bool stateWasAccepted = e->isAccepted();
357 if (wasAccepted)
358 e->accept();
359 else
360 e->ignore();
361
362 int identicalMatches = d->identicals.count();
363
364 switch(result) {
365 case QKeySequence::NoMatch:
366 return stateWasAccepted;
367 case QKeySequence::ExactMatch:
368 resetState();
369 dispatchEvent(e);
370 default:
371 break;
372 }
373 // If nextState is QKeySequence::ExactMatch && identicals.count == 0
374 // we've only found disabled shortcuts
375 return identicalMatches > 0 || result == QKeySequence::PartialMatch;
376}
377
378/*! \internal
379 Returns the next state of the statemachine
380 If return value is SequenceMatch::ExactMatch, then a call to matches()
381 will return a QObjects* list of all matching objects for the last matching
382 sequence.
383*/
384QKeySequence::SequenceMatch QShortcutMap::nextState(QKeyEvent *e)
385{
386 Q_D(QShortcutMap);
387 // Modifiers can NOT be shortcuts...
388 if (e->key() >= Qt::Key_Shift &&
389 e->key() <= Qt::Key_Alt)
390 return d->currentState;
391
392 QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
393
394 // We start fresh each time..
395 d->identicals.resize(0);
396
397 result = find(e);
398 if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) {
399 // If Shift + Key_Backtab, also try Shift + Qt::Key_Tab
400 if (e->key() == Qt::Key_Backtab) {
401 QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text());
402 result = find(&pe);
403 }
404 }
405
406 // Should we eat this key press?
407 if (d->currentState == QKeySequence::PartialMatch
408 || (d->currentState == QKeySequence::ExactMatch && d->identicals.count()))
409 e->accept();
410 // Does the new state require us to clean up?
411 if (result == QKeySequence::NoMatch)
412 clearSequence(d->currentSequences);
413 d->currentState = result;
414
415#if defined(DEBUG_QSHORTCUTMAP)
416 qDebug().nospace() << "QShortcutMap::nextState(" << e << ") = " << result;
417#endif
418 return result;
419}
420
421
422/*! \internal
423 Determines if an enabled shortcut has a matcing key sequence.
424*/
425bool QShortcutMap::hasShortcutForKeySequence(const QKeySequence &seq) const
426{
427 Q_D(const QShortcutMap);
428 QShortcutEntry entry(seq); // needed for searching
429 QList<QShortcutEntry>::ConstIterator itEnd = d->sequences.constEnd();
430 QList<QShortcutEntry>::ConstIterator it = qLowerBound(d->sequences.constBegin(), itEnd, entry);
431
432 for (;it != itEnd; ++it) {
433 if (matches(entry.keyseq, (*it).keyseq) == QKeySequence::ExactMatch && correctContext(*it) && (*it).enabled) {
434 return true;
435 }
436 }
437
438 //end of the loop: we didn't find anything
439 return false;
440}
441
442/*! \internal
443 Returns the next state of the statemachine, based
444 on the new key event \a e.
445 Matches are appended to the vector of identicals,
446 which can be access through matches().
447 \sa matches
448*/
449QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e)
450{
451 Q_D(QShortcutMap);
452 if (!d->sequences.count())
453 return QKeySequence::NoMatch;
454
455 createNewSequences(e, d->newEntries);
456#if defined(DEBUG_QSHORTCUTMAP)
457 qDebug() << "Possible shortcut key sequences:" << d->newEntries;
458#endif
459
460 // Should never happen
461 if (d->newEntries == d->currentSequences) {
462 Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().length(),
463 "QShortcutMap::find", "New sequence to find identical to previous");
464 return QKeySequence::NoMatch;
465 }
466
467 // Looking for new identicals, scrap old
468 d->identicals.resize(0);
469
470 bool partialFound = false;
471 bool identicalDisabledFound = false;
472 QVector<QKeySequence> okEntries;
473 int result = QKeySequence::NoMatch;
474 for (int i = d->newEntries.count()-1; i >= 0 ; --i) {
475 QShortcutEntry entry(d->newEntries.at(i)); // needed for searching
476 QList<QShortcutEntry>::ConstIterator itEnd = d->sequences.constEnd();
477 QList<QShortcutEntry>::ConstIterator it =
478 qLowerBound(d->sequences.constBegin(), itEnd, entry);
479
480 int oneKSResult = QKeySequence::NoMatch;
481 int tempRes = QKeySequence::NoMatch;
482 do {
483 if (it == itEnd)
484 break;
485 tempRes = matches(entry.keyseq, (*it).keyseq);
486 oneKSResult = qMax(oneKSResult, tempRes);
487 if (tempRes != QKeySequence::NoMatch && correctContext(*it)) {
488 if (tempRes == QKeySequence::ExactMatch) {
489 if ((*it).enabled)
490 d->identicals.append(&*it);
491 else
492 identicalDisabledFound = true;
493 } else if (tempRes == QKeySequence::PartialMatch) {
494 // We don't need partials, if we have identicals
495 if (d->identicals.size())
496 break;
497 // We only care about enabled partials, so we don't consume
498 // key events when all partials are disabled!
499 partialFound |= (*it).enabled;
500 }
501 }
502 ++it;
503 // If we got a valid match on this run, there might still be more keys to check against,
504 // so we'll loop once more. If we get NoMatch, there's guaranteed no more possible
505 // matches in the shortcutmap.
506 } while (tempRes != QKeySequence::NoMatch);
507
508 // If the type of match improves (ergo, NoMatch->Partial, or Partial->Exact), clear the
509 // previous list. If this match is equal or better than the last match, append to the list
510 if (oneKSResult > result) {
511 okEntries.clear();
512#if defined(DEBUG_QSHORTCUTMAP)
513 qDebug() << "Found better match (" << d->newEntries << "), clearing key sequence list";
514#endif
515 }
516 if (oneKSResult && oneKSResult >= result) {
517 okEntries << d->newEntries.at(i);
518#if defined(DEBUG_QSHORTCUTMAP)
519 qDebug() << "Added ok key sequence" << d->newEntries;
520#endif
521 }
522 }
523
524 if (d->identicals.size()) {
525 result = QKeySequence::ExactMatch;
526 } else if (partialFound) {
527 result = QKeySequence::PartialMatch;
528 } else if (identicalDisabledFound) {
529 result = QKeySequence::ExactMatch;
530 } else {
531 clearSequence(d->currentSequences);
532 result = QKeySequence::NoMatch;
533 }
534 if (result != QKeySequence::NoMatch)
535 d->currentSequences = okEntries;
536#if defined(DEBUG_QSHORTCUTMAP)
537 qDebug() << "Returning shortcut match == " << result;
538#endif
539 return QKeySequence::SequenceMatch(result);
540}
541
542/*! \internal
543 Clears \a seq to an empty QKeySequence.
544 Same as doing (the slower)
545 \snippet doc/src/snippets/code/src_gui_kernel_qshortcutmap.cpp 0
546*/
547void QShortcutMap::clearSequence(QVector<QKeySequence> &ksl)
548{
549 ksl.clear();
550 d_func()->newEntries.clear();
551}
552
553/*! \internal
554 Alters \a seq to the new sequence state, based on the
555 current sequence state, and the new key event \a e.
556*/
557void QShortcutMap::createNewSequences(QKeyEvent *e, QVector<QKeySequence> &ksl)
558{
559 Q_D(QShortcutMap);
560 QList<int> possibleKeys = QKeyMapper::possibleKeys(e);
561 int pkTotal = possibleKeys.count();
562 if (!pkTotal)
563 return;
564
565 int ssActual = d->currentSequences.count();
566 int ssTotal = qMax(1, ssActual);
567 // Resize to possible permutations of the current sequence(s).
568 ksl.resize(pkTotal * ssTotal);
569
570 int index = ssActual ? d->currentSequences.at(0).count() : 0;
571 for (int pkNum = 0; pkNum < pkTotal; ++pkNum) {
572 for (int ssNum = 0; ssNum < ssTotal; ++ssNum) {
573 int i = (pkNum * ssTotal) + ssNum;
574 QKeySequence &curKsl = ksl[i];
575 if (ssActual) {
576 const QKeySequence &curSeq = d->currentSequences.at(ssNum);
577 curKsl.setKey(curSeq[0], 0);
578 curKsl.setKey(curSeq[1], 1);
579 curKsl.setKey(curSeq[2], 2);
580 curKsl.setKey(curSeq[3], 3);
581 } else {
582 curKsl.setKey(0, 0);
583 curKsl.setKey(0, 1);
584 curKsl.setKey(0, 2);
585 curKsl.setKey(0, 3);
586 }
587 // Filtering keycode here with 0xdfffffff to ignore the Keypad modifier
588 curKsl.setKey(possibleKeys.at(pkNum) & 0xdfffffff, index);
589 }
590 }
591}
592
593/*! \internal
594 Basically the same function as QKeySequence::matches(const QKeySequence &seq) const
595 only that is specially handles Key_hyphen as Key_Minus, as people mix these up all the time and
596 they conceptually the same.
597*/
598QKeySequence::SequenceMatch QShortcutMap::matches(const QKeySequence &seq1,
599 const QKeySequence &seq2) const
600{
601 uint userN = seq1.count(),
602 seqN = seq2.count();
603
604 if (userN > seqN)
605 return QKeySequence::NoMatch;
606
607 // If equal in length, we have a potential ExactMatch sequence,
608 // else we already know it can only be partial.
609 QKeySequence::SequenceMatch match = (userN == seqN
610 ? QKeySequence::ExactMatch
611 : QKeySequence::PartialMatch);
612
613 for (uint i = 0; i < userN; ++i) {
614 int userKey = seq1[i],
615 sequenceKey = seq2[i];
616 if ((userKey & Qt::Key_unknown) == Qt::Key_hyphen)
617 userKey = (userKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
618 if ((sequenceKey & Qt::Key_unknown) == Qt::Key_hyphen)
619 sequenceKey = (sequenceKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
620 if (userKey != sequenceKey)
621 return QKeySequence::NoMatch;
622 }
623 return match;
624}
625
626/*! \internal
627 Returns true if the widget \a w is a logical sub window of the current
628 top-level widget.
629*/
630bool QShortcutMap::correctContext(const QShortcutEntry &item) const {
631 Q_ASSERT_X(item.owner, "QShortcutMap", "Shortcut has no owner. Illegal map state!");
632
633 QWidget *active_window = qApp->activeWindow();
634
635 // popups do not become the active window,
636 // so we fake it here to get the correct context
637 // for the shortcut system.
638 if (qApp->activePopupWidget())
639 active_window = qApp->activePopupWidget();
640
641 if (!active_window)
642 return false;
643#ifndef QT_NO_ACTION
644 if (QAction *a = qobject_cast<QAction *>(item.owner))
645 return correctContext(item.context, a, active_window);
646#endif
647#ifndef QT_NO_GRAPHICSVIEW
648 if (QGraphicsWidget *gw = qobject_cast<QGraphicsWidget *>(item.owner))
649 return correctGraphicsWidgetContext(item.context, gw, active_window);
650#endif
651 QWidget *w = qobject_cast<QWidget *>(item.owner);
652 if (!w) {
653 QShortcut *s = qobject_cast<QShortcut *>(item.owner);
654 w = s->parentWidget();
655 }
656 return correctWidgetContext(item.context, w, active_window);
657}
658
659bool QShortcutMap::correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window) const
660{
661 bool visible = w->isVisible();
662#ifdef Q_WS_MAC
663 if (!qt_mac_no_native_menubar && qobject_cast<QMenuBar *>(w))
664 visible = true;
665#endif
666
667 if (!visible || !w->isEnabled())
668 return false;
669
670 if (context == Qt::ApplicationShortcut)
671 return QApplicationPrivate::tryModalHelper(w, 0); // true, unless w is shadowed by a modal dialog
672
673 if (context == Qt::WidgetShortcut)
674 return w == QApplication::focusWidget();
675
676 if (context == Qt::WidgetWithChildrenShortcut) {
677 const QWidget *tw = QApplication::focusWidget();
678 while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup))
679 tw = tw->parentWidget();
680 return tw == w;
681 }
682
683 // Below is Qt::WindowShortcut context
684 QWidget *tlw = w->window();
685#ifndef QT_NO_GRAPHICSVIEW
686 if (QWExtra *topData = tlw->d_func()->extra) {
687 if (topData->proxyWidget) {
688 bool res = correctGraphicsWidgetContext(context, (QGraphicsWidget *)topData->proxyWidget, active_window);
689 return res;
690 }
691 }
692#endif
693
694 /* if a floating tool window is active, keep shortcuts on the
695 * parent working */
696 if (active_window != tlw && active_window && active_window->windowType() == Qt::Tool && active_window->parentWidget()) {
697 active_window = active_window->parentWidget()->window();
698 }
699
700 if (active_window != tlw)
701 return false;
702
703 /* if we live in a MDI subwindow, ignore the event if we are
704 not the active document window */
705 const QWidget* sw = w;
706 while (sw && !(sw->windowType() == Qt::SubWindow) && !sw->isWindow())
707 sw = sw->parentWidget();
708 if (sw && (sw->windowType() == Qt::SubWindow)) {
709 QWidget *focus_widget = QApplication::focusWidget();
710 while (focus_widget && focus_widget != sw)
711 focus_widget = focus_widget->parentWidget();
712 return sw == focus_widget;
713 }
714
715#if defined(DEBUG_QSHORTCUTMAP)
716 qDebug().nospace() << "..true [Pass-through]";
717#endif
718 return true;
719}
720
721#ifndef QT_NO_GRAPHICSVIEW
722bool QShortcutMap::correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsWidget *w, QWidget *active_window) const
723{
724 bool visible = w->isVisible();
725#ifdef Q_WS_MAC
726 if (!qt_mac_no_native_menubar && qobject_cast<QMenuBar *>(w))
727 visible = true;
728#endif
729
730 if (!visible || !w->isEnabled() || !w->scene())
731 return false;
732
733 if (context == Qt::ApplicationShortcut) {
734 // Applicationwide shortcuts are always reachable unless their owner
735 // is shadowed by modality. In QGV there's no modality concept, but we
736 // must still check if all views are shadowed.
737 QList<QGraphicsView *> views = w->scene()->views();
738 for (int i = 0; i < views.size(); ++i) {
739 if (QApplicationPrivate::tryModalHelper(views.at(i), 0))
740 return true;
741 }
742 return false;
743 }
744
745 if (context == Qt::WidgetShortcut)
746 return static_cast<QGraphicsItem *>(w) == w->scene()->focusItem();
747
748 if (context == Qt::WidgetWithChildrenShortcut) {
749 const QGraphicsItem *ti = w->scene()->focusItem();
750 if (ti && ti->isWidget()) {
751 const QGraphicsWidget *tw = static_cast<const QGraphicsWidget *>(ti);
752 while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup))
753 tw = tw->parentWidget();
754 return tw == w;
755 }
756 }
757
758 // Below is Qt::WindowShortcut context
759
760 // Find the active view (if any).
761 QList<QGraphicsView *> views = w->scene()->views();
762 QGraphicsView *activeView = 0;
763 for (int i = 0; i < views.size(); ++i) {
764 QGraphicsView *view = views.at(i);
765 if (view->window() == active_window) {
766 activeView = view;
767 break;
768 }
769 }
770 if (!activeView)
771 return false;
772
773 // The shortcut is reachable if owned by a windowless widget, or if the
774 // widget's window is the same as the focus item's window.
775 QGraphicsWidget *a = w->scene()->activeWindow();
776 return !w->window() || a == w->window();
777}
778#endif
779
780#ifndef QT_NO_ACTION
781bool QShortcutMap::correctContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window) const
782{
783 const QList<QWidget *> &widgets = a->d_func()->widgets;
784#if defined(DEBUG_QSHORTCUTMAP)
785 if (widgets.isEmpty())
786 qDebug() << a << "not connected to any widgets; won't trigger";
787#endif
788 for (int i = 0; i < widgets.size(); ++i) {
789 QWidget *w = widgets.at(i);
790#ifndef QT_NO_MENU
791 if (QMenu *menu = qobject_cast<QMenu *>(w)) {
792 QAction *a = menu->menuAction();
793 if (correctContext(context, a, active_window))
794 return true;
795 } else
796#endif
797 if (correctWidgetContext(context, w, active_window))
798 return true;
799 }
800
801#ifndef QT_NO_GRAPHICSVIEW
802 const QList<QGraphicsWidget *> &graphicsWidgets = a->d_func()->graphicsWidgets;
803#if defined(DEBUG_QSHORTCUTMAP)
804 if (graphicsWidgets.isEmpty())
805 qDebug() << a << "not connected to any widgets; won't trigger";
806#endif
807 for (int i = 0; i < graphicsWidgets.size(); ++i) {
808 QGraphicsWidget *w = graphicsWidgets.at(i);
809 if (correctGraphicsWidgetContext(context, w, active_window))
810 return true;
811 }
812#endif
813 return false;
814}
815#endif // QT_NO_ACTION
816
817/*! \internal
818 Converts keyboard button states into modifier states
819*/
820int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers)
821{
822 int result = 0;
823 if (modifiers & Qt::ShiftModifier)
824 result |= Qt::SHIFT;
825 if (modifiers & Qt::ControlModifier)
826 result |= Qt::CTRL;
827 if (modifiers & Qt::MetaModifier)
828 result |= Qt::META;
829 if (modifiers & Qt::AltModifier)
830 result |= Qt::ALT;
831 return result;
832}
833
834/*! \internal
835 Returns the vector of QShortcutEntry's matching the last Identical state.
836*/
837QVector<const QShortcutEntry*> QShortcutMap::matches() const
838{
839 Q_D(const QShortcutMap);
840 return d->identicals;
841}
842
843/*! \internal
844 Dispatches QShortcutEvents to widgets who grabbed the matched key sequence.
845*/
846void QShortcutMap::dispatchEvent(QKeyEvent *e)
847{
848 Q_D(QShortcutMap);
849 if (!d->identicals.size())
850 return;
851
852 const QKeySequence &curKey = d->identicals.at(0)->keyseq;
853 if (d->prevSequence != curKey) {
854 d->ambigCount = 0;
855 d->prevSequence = curKey;
856 }
857 // Find next
858 const QShortcutEntry *current = 0, *next = 0;
859 int i = 0, enabledShortcuts = 0;
860 while(i < d->identicals.size()) {
861 current = d->identicals.at(i);
862 if (current->enabled || !next){
863 ++enabledShortcuts;
864 if (enabledShortcuts > d->ambigCount + 1)
865 break;
866 next = current;
867 }
868 ++i;
869 }
870 d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1);
871 // Don't trigger shortcut if we're autorepeating and the shortcut is
872 // grabbed with not accepting autorepeats.
873 if (!next || (e->isAutoRepeat() && !next->autorepeat))
874 return;
875 // Dispatch next enabled
876#if defined(DEBUG_QSHORTCUTMAP)
877 qDebug().nospace()
878 << "QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\""
879 << (QString)next->keyseq << "\", " << next->id << ", "
880 << (bool)(enabledShortcuts>1) << ") to object(" << next->owner << ")";
881#endif
882 QShortcutEvent se(next->keyseq, next->id, enabledShortcuts>1);
883 QApplication::sendEvent(const_cast<QObject *>(next->owner), &se);
884}
885
886/* \internal
887 QShortcutMap dump function, only available when DEBUG_QSHORTCUTMAP is
888 defined.
889*/
890#if defined(Dump_QShortcutMap)
891void QShortcutMap::dumpMap() const
892{
893 Q_D(const QShortcutMap);
894 for (int i = 0; i < d->sequences.size(); ++i)
895 qDebug().nospace() << &(d->sequences.at(i));
896}
897#endif
898
899QT_END_NAMESPACE
900
901#endif // QT_NO_SHORTCUT
Note: See TracBrowser for help on using the repository browser.