43
43
#include "keysequencewidget.h"
45
45
KeySequenceButton::KeySequenceButton(KeySequenceWidget *d_, QWidget *parent)
46
: QPushButton(parent),
52
bool KeySequenceButton::event(QEvent *e) {
53
if(d->isRecording() && e->type() == QEvent::KeyPress) {
54
keyPressEvent(static_cast<QKeyEvent *>(e));
58
// The shortcut 'alt+c' ( or any other dialog local action shortcut )
59
// ended the recording and triggered the action associated with the
60
// action. In case of 'alt+c' ending the dialog. It seems that those
61
// ShortcutOverride events get sent even if grabKeyboard() is active.
62
if(d->isRecording() && e->type() == QEvent::ShortcutOverride) {
67
return QPushButton::event(e);
70
void KeySequenceButton::keyPressEvent(QKeyEvent *e) {
73
// Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key.
74
// We cannot do anything useful with those (several keys have -1, indistinguishable)
75
// and QKeySequence.toString() will also yield a garbage string.
76
QMessageBox::information(this,
77
tr("The key you just pressed is not supported by Qt."),
78
tr("Unsupported Key"));
79
return d->cancelRecording();
82
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
84
//don't have the return or space key appear as first key of the sequence when they
85
//were pressed to start editing - catch and them and imitate their effect
86
if(!d->isRecording() && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) {
88
d->_modifierKeys = newModifiers;
89
d->updateShortcutDisplay();
93
// We get events even if recording isn't active.
95
return QPushButton::keyPressEvent(e);
98
d->_modifierKeys = newModifiers;
101
case Qt::Key_AltGr: //or else we get unicode salad
104
case Qt::Key_Control:
107
case Qt::Key_Menu: //unused (yes, but why?)
108
d->updateShortcutDisplay();
112
if(!(d->_modifierKeys & ~Qt::SHIFT)) {
113
// It's the first key and no modifier pressed. Check if this is
115
if(!d->isOkWhenModifierless(keyQt))
119
// We now have a valid key press.
121
if((keyQt == Qt::Key_Backtab) && (d->_modifierKeys & Qt::SHIFT)) {
122
keyQt = Qt::Key_Tab | d->_modifierKeys;
124
else if(d->isShiftAsModifierAllowed(keyQt)) {
125
keyQt |= d->_modifierKeys;
127
keyQt |= (d->_modifierKeys & ~Qt::SHIFT);
129
d->_keySequence = QKeySequence(keyQt);
135
void KeySequenceButton::keyReleaseEvent(QKeyEvent *e) {
137
// ignore garbage, see keyPressEvent()
141
if(!d->isRecording())
142
return QPushButton::keyReleaseEvent(e);
146
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
148
// if a modifier that belongs to the shortcut was released...
149
if((newModifiers & d->_modifierKeys) < d->_modifierKeys) {
150
d->_modifierKeys = newModifiers;
151
d->updateShortcutDisplay();
46
: QPushButton(parent),
52
bool KeySequenceButton::event(QEvent *e)
54
if (d->isRecording() && e->type() == QEvent::KeyPress) {
55
keyPressEvent(static_cast<QKeyEvent *>(e));
59
// The shortcut 'alt+c' ( or any other dialog local action shortcut )
60
// ended the recording and triggered the action associated with the
61
// action. In case of 'alt+c' ending the dialog. It seems that those
62
// ShortcutOverride events get sent even if grabKeyboard() is active.
63
if (d->isRecording() && e->type() == QEvent::ShortcutOverride) {
68
return QPushButton::event(e);
72
void KeySequenceButton::keyPressEvent(QKeyEvent *e)
76
// Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key.
77
// We cannot do anything useful with those (several keys have -1, indistinguishable)
78
// and QKeySequence.toString() will also yield a garbage string.
79
QMessageBox::information(this,
80
tr("The key you just pressed is not supported by Qt."),
81
tr("Unsupported Key"));
82
return d->cancelRecording();
85
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
87
//don't have the return or space key appear as first key of the sequence when they
88
//were pressed to start editing - catch and them and imitate their effect
89
if (!d->isRecording() && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) {
91
d->_modifierKeys = newModifiers;
92
d->updateShortcutDisplay();
96
// We get events even if recording isn't active.
97
if (!d->isRecording())
98
return QPushButton::keyPressEvent(e);
101
d->_modifierKeys = newModifiers;
104
case Qt::Key_AltGr: //or else we get unicode salad
107
case Qt::Key_Control:
110
case Qt::Key_Menu: //unused (yes, but why?)
111
d->updateShortcutDisplay();
115
if (!(d->_modifierKeys & ~Qt::SHIFT)) {
116
// It's the first key and no modifier pressed. Check if this is
118
if (!d->isOkWhenModifierless(keyQt))
122
// We now have a valid key press.
124
if ((keyQt == Qt::Key_Backtab) && (d->_modifierKeys & Qt::SHIFT)) {
125
keyQt = Qt::Key_Tab | d->_modifierKeys;
127
else if (d->isShiftAsModifierAllowed(keyQt)) {
128
keyQt |= d->_modifierKeys;
131
keyQt |= (d->_modifierKeys & ~Qt::SHIFT);
133
d->_keySequence = QKeySequence(keyQt);
140
void KeySequenceButton::keyReleaseEvent(QKeyEvent *e)
142
if (e->key() == -1) {
143
// ignore garbage, see keyPressEvent()
147
if (!d->isRecording())
148
return QPushButton::keyReleaseEvent(e);
152
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
154
// if a modifier that belongs to the shortcut was released...
155
if ((newModifiers & d->_modifierKeys) < d->_modifierKeys) {
156
d->_modifierKeys = newModifiers;
157
d->updateShortcutDisplay();
155
162
/******************************************************************************/
157
164
KeySequenceWidget::KeySequenceWidget(QWidget *parent)
163
QHBoxLayout *layout = new QHBoxLayout(this);
164
layout->setMargin(0);
166
_keyButton = new KeySequenceButton(this, this);
167
_keyButton->setFocusPolicy(Qt::StrongFocus);
168
_keyButton->setIcon(SmallIcon("configure"));
169
_keyButton->setToolTip(tr("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+a: hold the Ctrl key and press a."));
170
layout->addWidget(_keyButton);
172
_clearButton = new QToolButton(this);
173
layout->addWidget(_clearButton);
175
if(qApp->isLeftToRight())
176
_clearButton->setIcon(SmallIcon("edit-clear-locationbar-rtl"));
178
_clearButton->setIcon(SmallIcon("edit-clear-locationbar-ltr"));
182
connect(_keyButton, SIGNAL(clicked()), SLOT(startRecording()));
183
connect(_keyButton, SIGNAL(clicked()), SIGNAL(clicked()));
184
connect(_clearButton, SIGNAL(clicked()), SLOT(clear()));
185
connect(_clearButton, SIGNAL(clicked()), SIGNAL(clicked()));
188
void KeySequenceWidget::setModel(ShortcutsModel *model) {
189
Q_ASSERT(!_shortcutsModel);
190
_shortcutsModel = model;
193
bool KeySequenceWidget::isOkWhenModifierless(int keyQt) const {
194
//this whole function is a hack, but especially the first line of code
195
if(QKeySequence(keyQt).toString().length() == 1)
202
case Qt::Key_Backtab: //does this ever happen?
203
case Qt::Key_Backspace:
211
bool KeySequenceWidget::isShiftAsModifierAllowed(int keyQt) const {
212
// Shift only works as a modifier with certain keys. It's not possible
213
// to enter the SHIFT+5 key sequence for me because this is handled as
214
// '%' by qt on my keyboard.
215
// The working keys are all hardcoded here :-(
216
if(keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35)
219
if(QChar(keyQt).isLetter())
225
case Qt::Key_Backspace:
228
case Qt::Key_ScrollLock:
231
case Qt::Key_PageDown:
247
void KeySequenceWidget::updateShortcutDisplay() {
248
QString s = _keySequence.toString(QKeySequence::NativeText);
249
s.replace('&', QLatin1String("&&"));
170
QHBoxLayout *layout = new QHBoxLayout(this);
171
layout->setMargin(0);
173
_keyButton = new KeySequenceButton(this, this);
174
_keyButton->setFocusPolicy(Qt::StrongFocus);
175
_keyButton->setIcon(SmallIcon("configure"));
176
_keyButton->setToolTip(tr("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+a: hold the Ctrl key and press a."));
177
layout->addWidget(_keyButton);
179
_clearButton = new QToolButton(this);
180
layout->addWidget(_clearButton);
182
if (qApp->isLeftToRight())
183
_clearButton->setIcon(SmallIcon("edit-clear-locationbar-rtl"));
185
_clearButton->setIcon(SmallIcon("edit-clear-locationbar-ltr"));
189
connect(_keyButton, SIGNAL(clicked()), SLOT(startRecording()));
190
connect(_keyButton, SIGNAL(clicked()), SIGNAL(clicked()));
191
connect(_clearButton, SIGNAL(clicked()), SLOT(clear()));
192
connect(_clearButton, SIGNAL(clicked()), SIGNAL(clicked()));
196
void KeySequenceWidget::setModel(ShortcutsModel *model)
198
Q_ASSERT(!_shortcutsModel);
199
_shortcutsModel = model;
203
bool KeySequenceWidget::isOkWhenModifierless(int keyQt) const
205
//this whole function is a hack, but especially the first line of code
206
if (QKeySequence(keyQt).toString().length() == 1)
213
case Qt::Key_Backtab: //does this ever happen?
214
case Qt::Key_Backspace:
223
bool KeySequenceWidget::isShiftAsModifierAllowed(int keyQt) const
225
// Shift only works as a modifier with certain keys. It's not possible
226
// to enter the SHIFT+5 key sequence for me because this is handled as
227
// '%' by qt on my keyboard.
228
// The working keys are all hardcoded here :-(
229
if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35)
232
if (QChar(keyQt).isLetter())
238
case Qt::Key_Backspace:
241
case Qt::Key_ScrollLock:
244
case Qt::Key_PageDown:
261
void KeySequenceWidget::updateShortcutDisplay()
263
QString s = _keySequence.toString(QKeySequence::NativeText);
264
s.replace('&', QLatin1String("&&"));
254
if(_modifierKeys & Qt::META) s += QChar(kControlUnicode);
255
if(_modifierKeys & Qt::ALT) s += QChar(kOptionUnicode);
256
if(_modifierKeys & Qt::SHIFT) s += QChar(kShiftUnicode);
257
if(_modifierKeys & Qt::CTRL) s += QChar(kCommandUnicode);
269
if (_modifierKeys & Qt::META) s += QChar(kControlUnicode);
270
if (_modifierKeys & Qt::ALT) s += QChar(kOptionUnicode);
271
if (_modifierKeys & Qt::SHIFT) s += QChar(kShiftUnicode);
272
if (_modifierKeys & Qt::CTRL) s += QChar(kCommandUnicode);
259
if(_modifierKeys & Qt::META) s += tr("Meta", "Meta key") + '+';
260
if(_modifierKeys & Qt::CTRL) s += tr("Ctrl", "Ctrl key") + '+';
261
if(_modifierKeys & Qt::ALT) s += tr("Alt", "Alt key") + '+';
262
if(_modifierKeys & Qt::SHIFT) s += tr("Shift", "Shift key") + '+';
274
if (_modifierKeys & Qt::META) s += tr("Meta", "Meta key") + '+';
275
if (_modifierKeys & Qt::CTRL) s += tr("Ctrl", "Ctrl key") + '+';
276
if (_modifierKeys & Qt::ALT) s += tr("Alt", "Alt key") + '+';
277
if (_modifierKeys & Qt::SHIFT) s += tr("Shift", "Shift key") + '+';
265
s = tr("Input", "What the user inputs now will be taken as the new shortcut");
267
// make it clear that input is still going on
272
s = tr("None", "No shortcut defined");
277
_keyButton->setText(s);
280
void KeySequenceWidget::startRecording() {
282
_oldKeySequence = _keySequence;
283
_keySequence = QKeySequence();
284
_conflictingIndex = QModelIndex();
286
_keyButton->grabKeyboard();
288
if(!QWidget::keyboardGrabber()) {
289
qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active";
292
_keyButton->setDown(true);
293
updateShortcutDisplay();
297
void KeySequenceWidget::doneRecording() {
298
bool wasRecording = _isRecording;
299
_isRecording = false;
300
_keyButton->releaseKeyboard();
301
_keyButton->setDown(false);
303
if(!wasRecording || _keySequence == _oldKeySequence) {
304
// The sequence hasn't changed
305
updateShortcutDisplay();
309
if(!isKeySequenceAvailable(_keySequence)) {
281
s = tr("Input", "What the user inputs now will be taken as the new shortcut");
283
// make it clear that input is still going on
288
s = tr("None", "No shortcut defined");
293
_keyButton->setText(s);
297
void KeySequenceWidget::startRecording()
300
_oldKeySequence = _keySequence;
301
_keySequence = QKeySequence();
302
_conflictingIndex = QModelIndex();
304
_keyButton->grabKeyboard();
306
if (!QWidget::keyboardGrabber()) {
307
qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active";
310
_keyButton->setDown(true);
311
updateShortcutDisplay();
315
void KeySequenceWidget::doneRecording()
317
bool wasRecording = _isRecording;
318
_isRecording = false;
319
_keyButton->releaseKeyboard();
320
_keyButton->setDown(false);
322
if (!wasRecording || _keySequence == _oldKeySequence) {
323
// The sequence hasn't changed
324
updateShortcutDisplay();
328
if (!isKeySequenceAvailable(_keySequence)) {
329
_keySequence = _oldKeySequence;
331
else if (wasRecording) {
332
emit keySequenceChanged(_keySequence, _conflictingIndex);
334
updateShortcutDisplay();
338
void KeySequenceWidget::cancelRecording()
310
340
_keySequence = _oldKeySequence;
311
} else if(wasRecording) {
312
emit keySequenceChanged(_keySequence, _conflictingIndex);
314
updateShortcutDisplay();
317
void KeySequenceWidget::cancelRecording() {
318
_keySequence = _oldKeySequence;
322
void KeySequenceWidget::setKeySequence(const QKeySequence &seq) {
323
// oldKeySequence holds the key sequence before recording started, if setKeySequence()
324
// is called while not recording then set oldKeySequence to the existing sequence so
325
// that the keySequenceChanged() signal is emitted if the new and previous key
326
// sequences are different
328
_oldKeySequence = _keySequence;
331
_clearButton->setVisible(!_keySequence.isEmpty());
335
void KeySequenceWidget::clear() {
336
setKeySequence(QKeySequence());
337
// setKeySequence() won't emit a signal when we're not recording
338
emit keySequenceChanged(QKeySequence());
341
bool KeySequenceWidget::isKeySequenceAvailable(const QKeySequence &seq) {
345
void KeySequenceWidget::setKeySequence(const QKeySequence &seq)
347
// oldKeySequence holds the key sequence before recording started, if setKeySequence()
348
// is called while not recording then set oldKeySequence to the existing sequence so
349
// that the keySequenceChanged() signal is emitted if the new and previous key
350
// sequences are different
352
_oldKeySequence = _keySequence;
355
_clearButton->setVisible(!_keySequence.isEmpty());
360
void KeySequenceWidget::clear()
362
setKeySequence(QKeySequence());
363
// setKeySequence() won't emit a signal when we're not recording
364
emit keySequenceChanged(QKeySequence());
368
bool KeySequenceWidget::isKeySequenceAvailable(const QKeySequence &seq)
373
// We need to access the root model, not the filtered one
374
for (int cat = 0; cat < _shortcutsModel->rowCount(); cat++) {
375
QModelIndex catIdx = _shortcutsModel->index(cat, 0);
376
for (int r = 0; r < _shortcutsModel->rowCount(catIdx); r++) {
377
QModelIndex actIdx = _shortcutsModel->index(r, 0, catIdx);
378
Q_ASSERT(actIdx.isValid());
379
if (actIdx.data(ShortcutsModel::ActiveShortcutRole).value<QKeySequence>() != seq)
382
if (!actIdx.data(ShortcutsModel::IsConfigurableRole).toBool()) {
383
QMessageBox::warning(this, tr("Shortcut Conflict"),
384
tr("The \"%1\" shortcut is already in use, and cannot be configured.\nPlease choose another one.").arg(seq.toString(QKeySequence::NativeText)),
389
QMessageBox box(QMessageBox::Warning, tr("Shortcut Conflict"),
390
(tr("The \"%1\" shortcut is ambiguous with the shortcut for the following action:")
391
+ "<br><ul><li>%2</li></ul><br>"
392
+ tr("Do you want to reassign this shortcut to the selected action?")
393
).arg(seq.toString(QKeySequence::NativeText), actIdx.data().toString()),
394
QMessageBox::Cancel, this);
395
box.addButton(tr("Reassign"), QMessageBox::AcceptRole);
396
if (box.exec() == QMessageBox::Cancel)
399
_conflictingIndex = actIdx;
345
// We need to access the root model, not the filtered one
346
for(int cat = 0; cat < _shortcutsModel->rowCount(); cat++) {
347
QModelIndex catIdx = _shortcutsModel->index(cat, 0);
348
for(int r = 0; r < _shortcutsModel->rowCount(catIdx); r++) {
349
QModelIndex actIdx = _shortcutsModel->index(r, 0, catIdx);
350
Q_ASSERT(actIdx.isValid());
351
if(actIdx.data(ShortcutsModel::ActiveShortcutRole).value<QKeySequence>() != seq)
354
if(!actIdx.data(ShortcutsModel::IsConfigurableRole).toBool()) {
355
QMessageBox::warning(this, tr("Shortcut Conflict"),
356
tr("The \"%1\" shortcut is already in use, and cannot be configured.\nPlease choose another one.").arg(seq.toString(QKeySequence::NativeText)),
361
QMessageBox box(QMessageBox::Warning, tr("Shortcut Conflict"),
362
(tr("The \"%1\" shortcut is ambiguous with the shortcut for the following action:")
363
+ "<br><ul><li>%2</li></ul><br>"
364
+ tr("Do you want to reassign this shortcut to the selected action?")
365
).arg(seq.toString(QKeySequence::NativeText), actIdx.data().toString()),
366
QMessageBox::Cancel, this);
367
box.addButton(tr("Reassign"), QMessageBox::AcceptRole);
368
if(box.exec() == QMessageBox::Cancel)
371
_conflictingIndex = actIdx;