1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the gui module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
#include "qshortcutmap_p.h"
30
#include "private/qobject_p.h"
31
#include "qkeysequence.h"
35
#include "qapplication.h"
38
#include "qshortcut.h"
39
#include "qapplication_p.h"
40
#include <private/qaction_p.h>
42
// To enable verbose output uncomment below
43
//#define Debug_QShortcutMap
46
Entry data for QShortcutMap
49
Pointer to parent owning the sequence
54
: keyseq(0), context(Qt::WindowShortcut), enabled(false), id(0), owner(0)
57
QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i)
58
: keyseq(k), context(c), enabled(true), id(i), owner(o)
61
bool operator<(const QShortcutEntry &f) const
62
{ return keyseq < f.keyseq; }
65
Qt::ShortcutContext context;
73
QDebug operator<< for easy debug output of the shortcut entries.
75
QDebug &operator<<(QDebug &dbg, const QShortcutEntry *se) {
77
return dbg << "QShortcutEntry(0x0)";
79
<< "QShortcutEntry(" << se->keyseq
80
<< "), id(" << se->id << "), enabled(" << se->enabled << ") owner(" << se->owner << ")";
86
Private data for QShortcutMap
88
class QShortcutMapPrivate
90
Q_DECLARE_PUBLIC(QShortcutMap)
93
QShortcutMapPrivate(QShortcutMap* parent)
94
: q_ptr(parent), currentId(0), ambigCount(0), currentState(QKeySequence::NoMatch)
95
{ identicals.reserve(10); }
96
QShortcutMap *q_ptr; // Private's parent
98
QList<QShortcutEntry> sequences; // All sequences!
100
int currentId; // Global shortcut ID number
101
int ambigCount; // Index of last enabled ambiguous dispatch
102
QKeySequence::SequenceMatch currentState;
103
QKeySequence currentSequence; // Sequence for the current state
104
QKeySequence prevSequence; // Sequence for the previous identical match
105
QVector<const QShortcutEntry*> identicals; // Last identical matches
110
QShortcutMap constructor.
112
QShortcutMap::QShortcutMap()
114
d_ptr = new QShortcutMapPrivate(this);
115
Q_ASSERT(d_ptr != 0);
120
QShortcutMap destructor.
122
QShortcutMap::~QShortcutMap()
129
Adds a shortcut to the global map.
130
Returns the id of the newly added shortcut.
132
int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &key, Qt::ShortcutContext context)
134
Q_ASSERT_X(owner, "QShortcutMap::addShortcut", "All shortcuts need an owner");
135
Q_ASSERT_X(!key.isEmpty(), "QShortcutMap::addShortcut", "Cannot add keyless shortcuts to map");
138
QShortcutEntry newEntry(owner, key, context, --(d->currentId));
139
QList<QShortcutEntry>::iterator it = qUpperBound(d->sequences.begin(), d->sequences.end(), newEntry);
140
d->sequences.insert(it, newEntry); // Insert sorted
141
#if defined(Debug_QShortcutMap)
143
<< "QShortcutMap::addShortcut(" << owner << ", "
144
<< key << ", " << context << ") = " << d->currentId;
150
Removes a shortcut from the global map.
151
If \a owner is 0, all entries in the map with the keysequence specified
152
is removed. If \a key is null, all sequences for \a owner is removed from
153
the map. If \a id is 0, any identical \a key sequences owned by \a owner
155
Returns the number of sequences removed from the map.
158
int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key)
161
int itemsRemoved = 0;
162
bool allOwners = (owner == 0);
163
bool allKeys = key.isEmpty();
164
bool allIds = id == 0;
166
// Special case, remove everything
167
if (allOwners && allKeys && id == 0) {
168
itemsRemoved = d->sequences.size();
169
d->sequences.clear();
173
int i = d->sequences.size()-1;
176
const QShortcutEntry &entry = d->sequences.at(i);
177
int entryId = entry.id;
178
if ((allOwners || entry.owner == owner)
179
&& (allIds || entry.id == id)
180
&& (allKeys || entry.keyseq == key)) {
181
d->sequences.removeAt(i);
188
#if defined(Debug_QShortcutMap)
190
<< "QShortcutMap::removeShortcut(" << id << ", " << owner << ", "
191
<< key << ") = " << itemsRemoved;
197
Changes the enable state of a shortcut to \a enable.
198
If \a owner is 0, all entries in the map with the keysequence specified
199
is removed. If \a key is null, all sequences for \a owner is removed from
200
the map. If \a id is 0, any identical \a key sequences owned by \a owner
202
Returns the number of sequences which are matched in the map.
204
int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &key)
207
int itemsChanged = 0;
208
bool allOwners = (owner == 0);
209
bool allKeys = key.isEmpty();
210
bool allIds = id == 0;
212
int i = d->sequences.size()-1;
215
QShortcutEntry entry = d->sequences.at(i);
216
if ((allOwners || entry.owner == owner)
217
&& (allIds || entry.id == id)
218
&& (allKeys || entry.keyseq == key)) {
219
d->sequences[i].enabled = enable;
226
#if defined(Debug_QShortcutMap)
228
<< "QShortcutMap::setShortcutEnabled(" << enable << ", " << id << ", "
229
<< owner << ", " << key << ") = " << itemsChanged;
236
Resets the state of the statemachine to NoMatch
238
void QShortcutMap::resetState()
241
d->currentState = QKeySequence::NoMatch;
242
clearSequence(d->currentSequence);
246
Returns the current state of the statemachine
248
QKeySequence::SequenceMatch QShortcutMap::state()
251
return d->currentState;
255
Uses ShortcutOverride event to see if any widgets want to override
256
the event. If not, uses nextState(QKeyEvent) to check for a grabbed
257
Shortcut, and dispatchEvent() is found an identical.
258
\sa nextState dispatchEvent
260
bool QShortcutMap::tryShortcutEvent(QWidget *w, QKeyEvent *e)
263
bool wasAccepted = e->isAccepted();
264
if (d->currentState == QKeySequence::NoMatch) {
265
ushort orgType = e->t;
266
e->t = QEvent::ShortcutOverride;
268
QApplication::sendSpontaneousEvent(w, e);
270
if (e->isAccepted()) {
277
QKeySequence::SequenceMatch result = nextState(e);
278
bool stateWasAccepted = e->isAccepted();
285
case QKeySequence::NoMatch:
286
return stateWasAccepted;
287
case QKeySequence::ExactMatch:
293
// If nextState is QKeySequence::ExactMatch && identicals.count == 0
294
// we've only found disabled shortcuts
295
return d->identicals.count() > 0 || result == QKeySequence::PartialMatch;
299
Returns the next state of the statemachine
300
If return value is SequenceMatch::ExactMatch, then a call to matches()
301
will return a QObjects* list of all matching objects for the last matching
304
QKeySequence::SequenceMatch QShortcutMap::nextState(QKeyEvent *e)
307
// Modifiers can NOT be shortcuts...
308
if (e->key() >= Qt::Key_Shift &&
309
e->key() <= Qt::Key_Alt)
310
return d->currentState;
312
QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
314
// We start fresh each time..
315
d->identicals.resize(0);
318
if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) {
319
// If Shift + Key_Backtab, also try Shift + Qt::Key_Tab
320
if (e->key() == Qt::Key_Backtab) {
321
QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text());
324
// If still no result, try removing the Shift modifier
325
if (result == QKeySequence::NoMatch) {
326
QKeyEvent pe = QKeyEvent(e->type(), e->key(),
327
e->modifiers()&~Qt::ShiftModifier, e->text());
332
// Should we eat this key press?
333
if (d->currentState == QKeySequence::PartialMatch
334
|| (d->currentState == QKeySequence::ExactMatch && d->identicals.count()))
336
// Does the new state require us to clean up?
337
if (result == QKeySequence::NoMatch)
338
clearSequence(d->currentSequence);
339
d->currentState = result;
341
#if defined(Debug_QShortcutMap)
342
qDebug().nospace() << "QShortcutMap::nextState(" << e << ") = " << result;
348
Returns the next state of the statemachine, based
349
on the new key event \a e.
350
Matches are appended to the vector of identicals,
351
which can be access through matches().
354
QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e)
357
if (!d->sequences.count())
358
return QKeySequence::NoMatch;
360
static QShortcutEntry newEntry;
361
createNewSequence(e, newEntry.keyseq);
363
// Should never happen
364
if (newEntry.keyseq == d->currentSequence) {
365
Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().length(),
366
"QShortcutMap::find", "New sequence to find identical to previous");
367
return QKeySequence::NoMatch;
370
// Looking for new identicals, scrap old
371
d->identicals.resize(0);
373
QList<QShortcutEntry>::ConstIterator itEnd = d->sequences.constEnd();
374
QList<QShortcutEntry>::ConstIterator it =
375
qLowerBound(d->sequences.constBegin(), itEnd, newEntry);
377
bool partialFound = false;
378
bool identicalDisabledFound = false;
379
QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
383
result = newEntry.keyseq.matches((*it).keyseq);
384
if (result != QKeySequence::NoMatch && correctContext(*it)) {
385
if (result == QKeySequence::ExactMatch) {
387
d->identicals.append(&*it);
389
identicalDisabledFound = true;
390
} else if (result == QKeySequence::PartialMatch) {
391
// We don't need partials, if we have identicals
392
if (d->identicals.size())
394
// We only care about enabled partials, so we don't consume
395
// key events when all partials are disabled!
396
partialFound |= (*it).enabled;
400
} while (result != QKeySequence::NoMatch);
402
if (d->identicals.size()) {
403
result = QKeySequence::ExactMatch;
404
} else if (partialFound) {
405
result = QKeySequence::PartialMatch;
406
} else if (identicalDisabledFound) {
407
result = QKeySequence::ExactMatch;
409
clearSequence(d->currentSequence);
410
result = QKeySequence::NoMatch;
412
if (result != QKeySequence::NoMatch)
413
d->currentSequence = newEntry.keyseq;
418
Clears \a seq to an empty QKeySequence.
419
Same as doing (the slower)
421
key = QKeySequence();
424
void QShortcutMap::clearSequence(QKeySequence &seq)
433
Alters \a seq to the new sequence state, based on the
434
current sequence state, and the new key event \a e.
436
void QShortcutMap::createNewSequence(QKeyEvent *e, QKeySequence &seq)
439
seq.setKey(d->currentSequence[0], 0);
440
seq.setKey(d->currentSequence[1], 1);
441
seq.setKey(d->currentSequence[2], 2);
442
seq.setKey(d->currentSequence[3], 3);
443
int index = d->currentSequence.count();
444
int modifier = translateModifiers(e->modifiers());
446
// Use the key code, if possible to prevent Ctrl+<Key> text problems,
447
// else use unicode of text
448
if (e->key() && e->key() != Qt::Key_unknown)
449
seq.setKey(e->key() | modifier, index);
451
seq.setKey((int)e->text().unicode()->toUpper().unicode() | modifier, index);
455
Returns true if the widget \a w is a logical sub window of the current
458
bool QShortcutMap::correctContext(const QShortcutEntry &item) {
459
Q_ASSERT_X(item.owner, "QShortcutMap", "Shortcut has no owner. Illegal map state!");
461
QWidget *active_window = qApp->activeWindow();
463
// popups do not become the active window,
464
// so we fake it here to get the correct context
465
// for the shortcut system.
466
if (qApp->activePopupWidget())
467
active_window = qApp->activePopupWidget();
472
if (QAction *a = qobject_cast<QAction *>(item.owner))
473
return correctContext(item.context, a, active_window);
475
QWidget *w = qobject_cast<QWidget *>(item.owner);
477
QShortcut *s = qobject_cast<QShortcut *>(item.owner);
478
w = s->parentWidget();
480
return correctContext(item.context, w, active_window);
483
bool QShortcutMap::correctContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window)
485
if (!w->isVisible() || !w->isEnabled())
488
if (context == Qt::ApplicationShortcut)
489
return QApplicationPrivate::tryModalHelper(w, 0); // true, unless w is shadowed by a modal dialog
491
if (context == Qt::WidgetShortcut)
492
return w == QApplication::focusWidget();
494
QWidget *tlw = w->window();
496
/* if a floating tool window is active, keep shortcuts on the
498
if (active_window != tlw && active_window && active_window->windowType() == Qt::Tool && active_window->parentWidget()) {
499
active_window = active_window->parentWidget()->window();
502
if (active_window != tlw)
505
/* if we live in a MDI subwindow, ignore the event if we are
506
not the active document window */
507
const QWidget* sw = w;
508
while (sw && !(sw->windowType() == Qt::SubWindow) && !sw->isWindow())
509
sw = sw->parentWidget();
510
if (sw && (sw->windowType() == Qt::SubWindow)) {
511
QWidget *focus_widget = QApplication::focusWidget();
512
while (focus_widget && focus_widget != sw)
513
focus_widget = focus_widget->parentWidget();
514
return sw == focus_widget;
517
#if defined(Debug_QShortcutMap)
518
qDebug().nospace() << "..true [Pass-through]";
524
bool QShortcutMap::correctContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window)
526
const QList<QWidget *> &widgets = a->d_func()->widgets;
527
for (int i = 0; i < widgets.size(); ++i) {
528
QWidget *w = widgets.at(i);
529
if (QMenu *menu = qobject_cast<QMenu *>(w)) {
530
QAction *a = menu->menuAction();
531
if (correctContext(context, a, active_window))
534
if (correctContext(context, w, active_window))
543
Converts keyboard button states into modifier states
545
int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers)
548
if (modifiers & Qt::ShiftModifier)
550
if (modifiers & Qt::ControlModifier)
552
if (modifiers & Qt::MetaModifier)
554
if (modifiers & Qt::AltModifier)
560
Returns the vector of QShortcutEntry's matching the last Identical state.
562
QVector<const QShortcutEntry*> QShortcutMap::matches() const
564
Q_D(const QShortcutMap);
565
return d->identicals;
569
Dispatches QShortcutEvents to widgets who grabbed the matched key sequence.
571
void QShortcutMap::dispatchEvent()
574
if (!d->identicals.size())
577
const QKeySequence &curKey = d->identicals.at(0)->keyseq;
578
if (d->prevSequence != curKey) {
580
d->prevSequence = curKey;
583
const QShortcutEntry *current = 0, *next = 0;
584
int i = 0, enabledShortcuts = 0;
585
while(i < d->identicals.size()) {
586
current = d->identicals.at(i);
587
if (current->enabled || !next){
589
if (enabledShortcuts > d->ambigCount + 1)
595
d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1);
598
// Dispatch next enabled
599
#if defined(Debug_QShortcutMap)
601
<< "QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\""
602
<< (QString)curKey << "\", " << next->id << ", "
603
<< (bool)(enabledShortcuts>1) << ") to object(" << next->owner << ")";
605
QShortcutEvent se(curKey, next->id, enabledShortcuts>1);
606
QApplication::sendEvent(const_cast<QObject *>(next->owner), &se);
610
QShortcutMap dump function, only available when Debug_QShortcutMap is
613
#if defined(Dump_QShortcutMap)
614
void QShortcutMap::dumpMap() const
616
Q_D(const QShortcutMap);
617
for (int i = 0; i < d->sequences.size(); ++i)
618
qDebug().nospace() << &(d->sequences.at(i));