mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
799 lines
25 KiB
C++
799 lines
25 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (C) 1998 Mark Donohoe <donohoe@kde.org>
|
|
Copyright (C) 2001 Ellis Whitehead <ellis@kde.org>
|
|
Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "kkeysequencewidget.h"
|
|
#include "kkeysequencewidget_p.h"
|
|
#include "kglobalaccel.h"
|
|
#include "kicon.h"
|
|
#include "klocale.h"
|
|
#include "kmessagebox.h"
|
|
#include "kaction.h"
|
|
#include "kactioncollection.h"
|
|
#include "kglobalsettings.h"
|
|
#include "kkeyserver.h"
|
|
#include "kdebug.h"
|
|
|
|
#include <QKeyEvent>
|
|
#include <QTimer>
|
|
#include <QHash>
|
|
#include <QHBoxLayout>
|
|
#include <QToolButton>
|
|
#include <QApplication>
|
|
|
|
class KKeySequenceWidgetPrivate
|
|
{
|
|
public:
|
|
KKeySequenceWidgetPrivate(KKeySequenceWidget *q);
|
|
|
|
void init();
|
|
|
|
static QKeySequence appendToSequence(const QKeySequence& seq, int keyQt);
|
|
static bool isOkWhenModifierless(int keyQt);
|
|
|
|
void updateShortcutDisplay();
|
|
void startRecording();
|
|
|
|
/**
|
|
* Conflicts the key sequence @a seq with a current standard
|
|
* shortcut?
|
|
*/
|
|
bool conflictWithStandardShortcuts(const QKeySequence &seq);
|
|
|
|
/**
|
|
* Conflicts the key sequence @a seq with a current local
|
|
* shortcut?
|
|
*/
|
|
bool conflictWithLocalShortcuts(const QKeySequence &seq);
|
|
|
|
/**
|
|
* Conflicts the key sequence @a seq with a current global
|
|
* shortcut?
|
|
*/
|
|
bool conflictWithGlobalShortcuts(const QKeySequence &seq);
|
|
|
|
/**
|
|
* Get permission to steal the shortcut @seq from the standard shortcut @a std.
|
|
*/
|
|
bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq);
|
|
|
|
bool checkAgainstStandardShortcuts() const
|
|
{
|
|
return checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts;
|
|
}
|
|
|
|
bool checkAgainstGlobalShortcuts() const
|
|
{
|
|
return checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts;
|
|
}
|
|
|
|
bool checkAgainstLocalShortcuts() const
|
|
{
|
|
return checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts;
|
|
}
|
|
|
|
void controlModifierlessTimout()
|
|
{
|
|
if (nKey != 0 && !modifierKeys) {
|
|
// No modifier key pressed currently. Start the timout
|
|
modifierlessTimeout.start(600);
|
|
} else {
|
|
// A modifier is pressed. Stop the timeout
|
|
modifierlessTimeout.stop();
|
|
}
|
|
}
|
|
|
|
void cancelRecording()
|
|
{
|
|
keySequence = oldKeySequence;
|
|
doneRecording();
|
|
}
|
|
|
|
bool promptStealShortcutSystemwide(QWidget *parent,
|
|
const QHash<QKeySequence, QList<KGlobalShortcutInfo> > &shortcuts,
|
|
const QKeySequence &sequence)
|
|
{
|
|
if (shortcuts.isEmpty()) {
|
|
// Usage error. Just say no
|
|
return false;
|
|
}
|
|
|
|
QString clashingKeys = "";
|
|
Q_FOREACH (const QKeySequence &seq, shortcuts.keys()) {
|
|
Q_FOREACH (const KGlobalShortcutInfo &info, shortcuts[seq]) {
|
|
clashingKeys += i18n(
|
|
"Shortcut '%1' in Application %2 for action %3\n",
|
|
seq.toString(),
|
|
info.componentFriendlyName,
|
|
info.friendlyName
|
|
);
|
|
}
|
|
}
|
|
|
|
const int hashSize = shortcuts.size();
|
|
|
|
QString message = i18ncp(
|
|
"%1 is the number of conflicts (hidden), %2 is the key sequence of the shortcut that is problematic",
|
|
"The shortcut '%2' conflicts with the following key combination:\n",
|
|
"The shortcut '%2' conflicts with the following key combinations:\n",
|
|
hashSize, sequence.toString()
|
|
);
|
|
message+=clashingKeys;
|
|
|
|
QString title = i18ncp(
|
|
"%1 is the number of shortcuts with which there is a conflict",
|
|
"Conflict with Registered Global Shortcut", "Conflict with Registered Global Shortcuts", hashSize
|
|
);
|
|
|
|
return KMessageBox::warningContinueCancel(parent, message, title, KGuiItem(i18n("Reassign"))) == KMessageBox::Continue;
|
|
}
|
|
|
|
|
|
// private slot
|
|
void doneRecording(bool validate = true);
|
|
|
|
// members
|
|
KKeySequenceWidget *const q;
|
|
QHBoxLayout *layout;
|
|
KKeySequenceButton *keyButton;
|
|
QToolButton *clearButton;
|
|
|
|
QKeySequence keySequence;
|
|
QKeySequence oldKeySequence;
|
|
QTimer modifierlessTimeout;
|
|
bool allowModifierless;
|
|
uint nKey;
|
|
uint modifierKeys;
|
|
bool isRecording;
|
|
bool multiKeyShortcutsAllowed;
|
|
QString componentName;
|
|
|
|
//! Check the key sequence against KStandardShortcut::find()
|
|
KKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes;
|
|
|
|
/**
|
|
* The action to never consider when checking for conflict shortcut
|
|
*/
|
|
QAction* associatedAction;
|
|
|
|
/**
|
|
* The list of action collections to check against for conflict shortcut
|
|
*/
|
|
QList<KActionCollection*> checkActionCollections;
|
|
|
|
/**
|
|
* The action to steal the shortcut from.
|
|
*/
|
|
QList<KAction*> stealActions;
|
|
|
|
bool stealShortcuts(const QList<KAction *> &actions, const QKeySequence &seq);
|
|
void wontStealShortcut(QAction *item, const QKeySequence &seq);
|
|
};
|
|
|
|
KKeySequenceWidgetPrivate::KKeySequenceWidgetPrivate(KKeySequenceWidget *q)
|
|
: q(q),
|
|
layout(nullptr),
|
|
keyButton(nullptr),
|
|
clearButton(nullptr),
|
|
allowModifierless(false),
|
|
nKey(0),
|
|
modifierKeys(0),
|
|
isRecording(false),
|
|
multiKeyShortcutsAllowed(true),
|
|
checkAgainstShortcutTypes(KKeySequenceWidget::LocalShortcuts & KKeySequenceWidget::GlobalShortcuts),
|
|
associatedAction(nullptr)
|
|
{
|
|
}
|
|
|
|
void KKeySequenceWidgetPrivate::init()
|
|
{
|
|
layout = new QHBoxLayout(q);
|
|
layout->setMargin(0);
|
|
|
|
keyButton = new KKeySequenceButton(this, q);
|
|
keyButton->setFocusPolicy(Qt::StrongFocus);
|
|
keyButton->setIcon(KIcon("configure"));
|
|
keyButton->setToolTip(
|
|
i18n(
|
|
"Click on the button, then enter the shortcut like you would in the program.\n"
|
|
"Example for Ctrl+a: hold the Ctrl key and press a."
|
|
)
|
|
);
|
|
layout->addWidget(keyButton);
|
|
|
|
clearButton = new QToolButton(q);
|
|
layout->addWidget(clearButton);
|
|
|
|
if (qApp->isLeftToRight()) {
|
|
clearButton->setIcon(KIcon("edit-clear-locationbar-rtl"));
|
|
} else {
|
|
clearButton->setIcon(KIcon("edit-clear-locationbar-ltr"));
|
|
}
|
|
}
|
|
|
|
QKeySequence KKeySequenceWidgetPrivate::appendToSequence(const QKeySequence &seq, int keyQt)
|
|
{
|
|
if (seq[0] == keyQt || seq[1] == keyQt) {
|
|
// no change in sequence (one of the sequences matches)
|
|
return seq;
|
|
} else if (seq[0] == 0) {
|
|
// no primary
|
|
return QKeySequence(keyQt, seq[1]);
|
|
}
|
|
// whatever the alternative is it is replaced
|
|
return QKeySequence(seq[0], keyQt);
|
|
}
|
|
|
|
bool KKeySequenceWidgetPrivate::isOkWhenModifierless(int keyQt)
|
|
{
|
|
// this whole function is a hack, but especially the first line of code
|
|
if (QKeySequence(keyQt).toString().length() == 1) {
|
|
return false;
|
|
}
|
|
|
|
switch (keyQt) {
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Space:
|
|
case Qt::Key_Tab:
|
|
case Qt::Key_Backtab: //does this ever happen?
|
|
case Qt::Key_Backspace:
|
|
case Qt::Key_Delete: {
|
|
return false;
|
|
}
|
|
default: {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void KKeySequenceWidgetPrivate::startRecording()
|
|
{
|
|
nKey = 0;
|
|
modifierKeys = 0;
|
|
oldKeySequence = keySequence;
|
|
keySequence = QKeySequence();
|
|
isRecording = true;
|
|
keyButton->grabKeyboard();
|
|
KGlobalSettings::emitChange(KGlobalSettings::BlockShortcuts, 1);
|
|
|
|
if (!QWidget::keyboardGrabber()) {
|
|
kWarning() << "Failed to grab the keyboard! Most likely Katie's nograb option is active";
|
|
}
|
|
|
|
keyButton->setDown(true);
|
|
updateShortcutDisplay();
|
|
}
|
|
|
|
void KKeySequenceWidgetPrivate::doneRecording(bool validate)
|
|
{
|
|
modifierlessTimeout.stop();
|
|
isRecording = false;
|
|
keyButton->releaseKeyboard();
|
|
keyButton->setDown(false);
|
|
KGlobalSettings::emitChange(KGlobalSettings::BlockShortcuts, 0);
|
|
stealActions.clear();
|
|
|
|
if (keySequence == oldKeySequence) {
|
|
// The sequence hasn't changed
|
|
updateShortcutDisplay();
|
|
return;
|
|
}
|
|
|
|
if (validate && !q->isKeySequenceAvailable(keySequence)) {
|
|
// The sequence had conflicts and the user said no to stealing it
|
|
keySequence = oldKeySequence;
|
|
} else {
|
|
emit q->keySequenceChanged(keySequence);
|
|
}
|
|
|
|
updateShortcutDisplay();
|
|
}
|
|
|
|
bool KKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence)
|
|
{
|
|
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts)) {
|
|
return false;
|
|
}
|
|
|
|
// Global shortcuts are on key+modifier shortcuts. They can clash with each of the keys of a
|
|
// multi key shortcut.
|
|
KGlobalAccel* kglobalaccel = KGlobalAccel::self();
|
|
QHash<QKeySequence, QList<KGlobalShortcutInfo> > others;
|
|
for (int i = 0; i < keySequence.count(); ++i) {
|
|
QKeySequence tmp(keySequence[i]);
|
|
if (!kglobalaccel->isGlobalShortcutAvailable(tmp, associatedAction)) {
|
|
QList<KGlobalShortcutInfo> globalinfo = kglobalaccel->getGlobalShortcutsByKey(tmp);
|
|
if (!globalinfo.isEmpty()) {
|
|
others.insert(tmp, globalinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!others.isEmpty() && !promptStealShortcutSystemwide(q, others, keySequence)) {
|
|
return true;
|
|
}
|
|
|
|
// The user approved stealing the shortcut. We have to steal
|
|
// it immediately because KAction::setGlobalShortcut() refuses
|
|
// to set a global shortcut that is already used. There is no
|
|
// error it just silently fails. So be nice because this is
|
|
// most likely the first action that is done in the slot
|
|
// listening to keySequenceChanged().
|
|
kglobalaccel->stealShortcutSystemwide(keySequence, associatedAction);
|
|
return false;
|
|
}
|
|
|
|
bool KKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence)
|
|
{
|
|
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts)) {
|
|
return false;
|
|
}
|
|
|
|
// Add all the actions to a single list to be able to process them in a single loop below.
|
|
// Note that this can't be done in setCheckActionCollections(), because pointers to the
|
|
// collections actions are kep, and between the call to setCheckActionCollections() and this function
|
|
// some actions might already be removed from the collection again.
|
|
QList<QAction*> allActions;
|
|
foreach (KActionCollection* collection, checkActionCollections) {
|
|
allActions += collection->actions();
|
|
}
|
|
|
|
// Because of multikey shortcuts we can have clashes with many shortcuts.
|
|
//
|
|
// Example 1:
|
|
//
|
|
// Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F'
|
|
// and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as
|
|
// 'activatedAmbiguously()' for obvious reasons.
|
|
//
|
|
// Example 2:
|
|
//
|
|
// Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'.
|
|
// This will shadow 'CTRL-X' for the same reason as above.
|
|
//
|
|
// Example 3:
|
|
//
|
|
// Some weird combination of Example 1 and 2 with three shortcuts using
|
|
// 1/2/3 key shortcuts. I think you can imagine.
|
|
QList<KAction*> conflictingActions;
|
|
|
|
// find conflicting shortcuts with existing actions
|
|
foreach (QAction* qaction , allActions) {
|
|
if (qaction == associatedAction) {
|
|
// the action shall not conflict with itself
|
|
continue;
|
|
}
|
|
|
|
KAction *kaction = qobject_cast<KAction*>(qaction);
|
|
if (kaction) {
|
|
if (kaction->shortcut().matches(keySequence) != QKeySequence::NoMatch) {
|
|
// A conflict with a KAction. If that action is configurable ask the user what to
|
|
// do. If not reject this keySequence.
|
|
if (kaction->isShortcutConfigurable()) {
|
|
conflictingActions.append(kaction);
|
|
} else {
|
|
wontStealShortcut(kaction, keySequence);
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
if (qaction->shortcut() == keySequence) {
|
|
// A conflict with a QAction, does not have a configurable option so changing its
|
|
// shortcut from here is a bad idea even tho its shortcut may be saved and restored
|
|
// from config somewhere
|
|
wontStealShortcut(qaction, keySequence);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (conflictingActions.isEmpty()) {
|
|
// No conflicting shortcuts found.
|
|
return false;
|
|
}
|
|
|
|
if (stealShortcuts(conflictingActions, keySequence)) {
|
|
stealActions = conflictingActions;
|
|
// Announce that the user
|
|
Q_FOREACH (KAction *stealAction, stealActions) {
|
|
emit q->stealShortcut(keySequence, stealAction);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence)
|
|
{
|
|
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts)) {
|
|
return false;
|
|
}
|
|
|
|
KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence);
|
|
if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool KKeySequenceWidgetPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
|
|
{
|
|
QString title = i18n("Conflict with Standard Application Shortcut");
|
|
QString message = i18n(
|
|
"The '%1' key combination is also used for the standard action "
|
|
"\"%2\" that some applications use.\n"
|
|
"Do you really want to use it as a global shortcut as well?",
|
|
seq.toString(QKeySequence::NativeText), KStandardShortcut::label(std)
|
|
);
|
|
|
|
if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void KKeySequenceWidgetPrivate::updateShortcutDisplay()
|
|
{
|
|
// empty string if no non-modifier was pressed
|
|
QString s = keySequence.toString(QKeySequence::NativeText);
|
|
s.replace('&', QLatin1String("&&"));
|
|
|
|
if (isRecording) {
|
|
if (modifierKeys) {
|
|
if (!s.isEmpty()) s.append(",");
|
|
if (modifierKeys & Qt::META) s += KKeyServer::modToStringUser(Qt::META) + '+';
|
|
#if defined(Q_WS_X11)
|
|
if (modifierKeys & Qt::CTRL) s += KKeyServer::modToStringUser(Qt::CTRL) + '+';
|
|
if (modifierKeys & Qt::ALT) s += KKeyServer::modToStringUser(Qt::ALT) + '+';
|
|
#endif
|
|
if (modifierKeys & Qt::SHIFT) s += KKeyServer::modToStringUser(Qt::SHIFT) + '+';
|
|
|
|
} else if (nKey == 0) {
|
|
s = i18nc("What the user inputs now will be taken as the new shortcut", "Input");
|
|
}
|
|
// make it clear that input is still going on
|
|
s.append(" ...");
|
|
}
|
|
|
|
if (s.isEmpty()) {
|
|
s = i18nc("No shortcut defined", "None");
|
|
}
|
|
|
|
s.prepend(' ');
|
|
s.append(' ');
|
|
keyButton->setText(s);
|
|
}
|
|
|
|
bool KKeySequenceWidgetPrivate::stealShortcuts(const QList<KAction *> &actions,
|
|
const QKeySequence &seq)
|
|
{
|
|
|
|
const int listSize = actions.size();
|
|
|
|
QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize);
|
|
|
|
QString conflictingShortcuts;
|
|
Q_FOREACH(const KAction *action, actions) {
|
|
conflictingShortcuts += i18n(
|
|
"Shortcut '%1' for action '%2'\n",
|
|
action->shortcut().toString(QKeySequence::NativeText),
|
|
KGlobal::locale()->removeAcceleratorMarker(action->text())
|
|
);
|
|
}
|
|
QString message = i18ncp(
|
|
"%1 is the number of ambigious shortcut clashes (hidden)",
|
|
"The \"%2\" shortcut is ambiguous with the following shortcut.\n"
|
|
"Do you want to assign an empty shortcut to this action?\n"
|
|
"%3",
|
|
"The \"%2\" shortcut is ambiguous with the following shortcuts.\n"
|
|
"Do you want to assign an empty shortcut to these actions?\n"
|
|
"%3",
|
|
listSize,
|
|
seq.toString(QKeySequence::NativeText),
|
|
conflictingShortcuts
|
|
);
|
|
|
|
if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void KKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq)
|
|
{
|
|
QString title(i18n("Shortcut conflict"));
|
|
QString msg(
|
|
i18n( "<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>"
|
|
"Please select a different one.</qt>", seq.toString(QKeySequence::NativeText) ,
|
|
KGlobal::locale()->removeAcceleratorMarker(item->text()))
|
|
);
|
|
KMessageBox::sorry(q, msg);
|
|
}
|
|
|
|
KKeySequenceWidget::KKeySequenceWidget(QWidget *parent)
|
|
: QWidget(parent),
|
|
d(new KKeySequenceWidgetPrivate(this))
|
|
{
|
|
d->init();
|
|
setFocusProxy(d->keyButton);
|
|
connect(d->keyButton, SIGNAL(clicked()), this, SLOT(captureKeySequence()));
|
|
connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearKeySequence()));
|
|
connect(&d->modifierlessTimeout, SIGNAL(timeout()), this, SLOT(doneRecording()));
|
|
d->updateShortcutDisplay();
|
|
}
|
|
|
|
KKeySequenceWidget::~KKeySequenceWidget()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
KKeySequenceWidget::ShortcutTypes KKeySequenceWidget::checkForConflictsAgainst() const
|
|
{
|
|
return d->checkAgainstShortcutTypes;
|
|
}
|
|
|
|
void KKeySequenceWidget::setComponentName(const QString &componentName)
|
|
{
|
|
d->componentName = componentName;
|
|
}
|
|
|
|
void KKeySequenceWidget::setAssociatedAction(QAction *action)
|
|
{
|
|
d->associatedAction = action;
|
|
}
|
|
|
|
QAction* KKeySequenceWidget::associatedAction()
|
|
{
|
|
return d->associatedAction;
|
|
}
|
|
|
|
bool KKeySequenceWidget::multiKeyShortcutsAllowed() const
|
|
{
|
|
return d->multiKeyShortcutsAllowed;
|
|
}
|
|
|
|
void KKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed)
|
|
{
|
|
d->multiKeyShortcutsAllowed = allowed;
|
|
}
|
|
|
|
|
|
void KKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types)
|
|
{
|
|
d->checkAgainstShortcutTypes = types;
|
|
}
|
|
|
|
void KKeySequenceWidget::setModifierlessAllowed(bool allow)
|
|
{
|
|
d->allowModifierless = allow;
|
|
}
|
|
|
|
bool KKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const
|
|
{
|
|
if (keySequence.isEmpty()) {
|
|
return true;
|
|
}
|
|
return !(d->conflictWithLocalShortcuts(keySequence)
|
|
|| d->conflictWithGlobalShortcuts(keySequence)
|
|
|| d->conflictWithStandardShortcuts(keySequence));
|
|
}
|
|
|
|
bool KKeySequenceWidget::isModifierlessAllowed()
|
|
{
|
|
return d->allowModifierless;
|
|
}
|
|
|
|
void KKeySequenceWidget::setClearButtonShown(bool show)
|
|
{
|
|
d->clearButton->setVisible(show);
|
|
}
|
|
|
|
void KKeySequenceWidget::setCheckActionCollections(const QList<KActionCollection *>& actionCollections)
|
|
{
|
|
d->checkActionCollections = actionCollections;
|
|
}
|
|
|
|
void KKeySequenceWidget::captureKeySequence()
|
|
{
|
|
d->startRecording();
|
|
}
|
|
|
|
QKeySequence KKeySequenceWidget::keySequence() const
|
|
{
|
|
return d->keySequence;
|
|
}
|
|
|
|
void KKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate)
|
|
{
|
|
// oldKeySequence holds the key sequence before recording started, if setKeySequence()
|
|
// is called while not recording then set oldKeySequence to the existing sequence so
|
|
// that the keySequenceChanged() signal is emitted if the new and previous key
|
|
// sequences are different
|
|
if (!d->isRecording) {
|
|
d->oldKeySequence = d->keySequence;
|
|
}
|
|
d->keySequence = seq;
|
|
d->doneRecording(validate == Validate);
|
|
}
|
|
|
|
void KKeySequenceWidget::clearKeySequence()
|
|
{
|
|
setKeySequence(QKeySequence());
|
|
}
|
|
|
|
void KKeySequenceWidget::applyStealShortcut()
|
|
{
|
|
QSet<KActionCollection *> changedCollections;
|
|
|
|
Q_FOREACH (KAction *stealAction, d->stealActions) {
|
|
// Stealing a shortcut means setting it to an empty one.
|
|
stealAction->setShortcut(QKeySequence(), KAction::ActiveShortcut);
|
|
|
|
// The following code will find the action we are about to
|
|
// steal from and save it's actioncollection.
|
|
KActionCollection* parentCollection = 0;
|
|
foreach(KActionCollection* collection, d->checkActionCollections) {
|
|
if (collection->actions().contains(stealAction)) {
|
|
parentCollection = collection;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remember the changed collection
|
|
if (parentCollection) {
|
|
changedCollections.insert(parentCollection);
|
|
}
|
|
}
|
|
|
|
Q_FOREACH (KActionCollection *col, changedCollections) {
|
|
col->writeSettings();
|
|
}
|
|
|
|
d->stealActions.clear();
|
|
}
|
|
|
|
// prevent Katie from special casing Tab and Backtab
|
|
bool KKeySequenceButton::event(QEvent* e)
|
|
{
|
|
// The shortcut 'alt+c' ( or any other dialog local action shortcut )
|
|
// ended the recording and triggered the action associated with the
|
|
// action. In case of 'alt+c' ending the dialog. It seems that those
|
|
// ShortcutOverride events get sent even if grabKeyboard() is active.
|
|
if (d->isRecording && e->type() == QEvent::ShortcutOverride) {
|
|
e->accept();
|
|
return true;
|
|
}
|
|
|
|
if (d->isRecording && e->type() == QEvent::KeyPress) {
|
|
keyPressEvent(static_cast<QKeyEvent *>(e));
|
|
return true;
|
|
}
|
|
|
|
return QPushButton::event(e);
|
|
}
|
|
|
|
void KKeySequenceButton::keyPressEvent(QKeyEvent *e)
|
|
{
|
|
int keyQt = e->key();
|
|
if (keyQt <= 0) {
|
|
// Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key.
|
|
// We cannot do anything useful with those (several keys have -1, indistinguishable)
|
|
// and QKeySequence.toString() will also yield a garbage string.
|
|
KMessageBox::sorry(this,
|
|
i18n("The key you just pressed is not supported by Qt."),
|
|
i18n("Unsupported Key"));
|
|
return d->cancelRecording();
|
|
}
|
|
|
|
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
|
|
|
|
//don't have the return or space key appear as first key of the sequence when they
|
|
//were pressed to start editing - catch and them and imitate their effect
|
|
if (!d->isRecording && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) {
|
|
d->startRecording();
|
|
d->modifierKeys = newModifiers;
|
|
d->updateShortcutDisplay();
|
|
return;
|
|
}
|
|
|
|
// We get events even if recording isn't active.
|
|
if (!d->isRecording) {
|
|
return QPushButton::keyPressEvent(e);
|
|
}
|
|
|
|
e->accept();
|
|
d->modifierKeys = newModifiers;
|
|
|
|
switch (keyQt) {
|
|
case Qt::Key_AltGr: {
|
|
// or else its unicode salad
|
|
return;
|
|
}
|
|
case Qt::Key_Shift:
|
|
case Qt::Key_Control:
|
|
case Qt::Key_Alt:
|
|
case Qt::Key_Meta:
|
|
case Qt::Key_Menu: {
|
|
// unused (yes, but why?)
|
|
d->controlModifierlessTimout();
|
|
d->updateShortcutDisplay();
|
|
break;
|
|
}
|
|
default: {
|
|
if (d->nKey == 0 && !(d->modifierKeys & ~Qt::SHIFT)) {
|
|
// It's the first key and no modifier pressed. Check if this is
|
|
// allowed
|
|
if (!(KKeySequenceWidgetPrivate::isOkWhenModifierless(keyQt)
|
|
|| d->allowModifierless)) {
|
|
// No it's not
|
|
return;
|
|
}
|
|
}
|
|
|
|
// valid key press.
|
|
if (keyQt) {
|
|
if ((keyQt == Qt::Key_Backtab) && (d->modifierKeys & Qt::SHIFT)) {
|
|
keyQt = Qt::Key_Tab | d->modifierKeys;
|
|
} else if (KKeyServer::isShiftAsModifierAllowed(keyQt)) {
|
|
keyQt |= d->modifierKeys;
|
|
} else {
|
|
keyQt |= (d->modifierKeys & ~Qt::SHIFT);
|
|
}
|
|
|
|
d->keySequence = KKeySequenceWidgetPrivate::appendToSequence(d->oldKeySequence, keyQt);
|
|
|
|
d->nKey++;
|
|
if ((!d->multiKeyShortcutsAllowed) || (d->nKey >= 4)) {
|
|
d->doneRecording();
|
|
return;
|
|
}
|
|
d->controlModifierlessTimout();
|
|
d->updateShortcutDisplay();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KKeySequenceButton::keyReleaseEvent(QKeyEvent *e)
|
|
{
|
|
if (e->key() <= 0) {
|
|
// ignore garbage, see keyPressEvent()
|
|
return;
|
|
}
|
|
|
|
if (!d->isRecording) {
|
|
return QPushButton::keyReleaseEvent(e);
|
|
}
|
|
|
|
e->accept();
|
|
|
|
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
|
|
|
|
//if a modifier that belongs to the shortcut was released...
|
|
if ((newModifiers & d->modifierKeys) < d->modifierKeys) {
|
|
d->modifierKeys = newModifiers;
|
|
d->controlModifierlessTimout();
|
|
d->updateShortcutDisplay();
|
|
}
|
|
}
|
|
|
|
#include "moc_kkeysequencewidget.cpp"
|
|
#include "moc_kkeysequencewidget_p.cpp"
|