Make it possible to set more than one shortcut per action

Multiple shortcuts per action make it possible to configure friendlier
behavior.
E.g. on macOS decreasing/increasing font sizes, and deleting elements
usually have two shortcuts each. But also custom configurations that
assign a different key to a common function without removing the default
can be useful.

In this patch the functionality is still pretty much hidden from the
user, even though there is a "secret" way to enter such multiple
shortcuts in the settings dialog, by separating shortcuts with
" | ".

Task-number: QTCREATORBUG-72
Change-Id: I16bec0a71aaf4abf50335b0fd7da620c73b31777
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Eike Ziller
2020-03-04 16:37:45 +01:00
parent 2307c61c95
commit 31cc9b8e69
8 changed files with 204 additions and 84 deletions

View File

@@ -30,6 +30,7 @@
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/id.h> #include <coreplugin/id.h>
#include <utils/algorithm.h>
#include <utils/fadingindicator.h> #include <utils/fadingindicator.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -46,6 +47,7 @@ namespace {
} }
static const char kKeyboardSettingsKey[] = "KeyboardShortcuts"; static const char kKeyboardSettingsKey[] = "KeyboardShortcuts";
static const char kKeyboardSettingsKeyV2[] = "KeyboardShortcutsV2";
using namespace Core; using namespace Core;
using namespace Core::Internal; using namespace Core::Internal;
@@ -495,22 +497,50 @@ Action *ActionManagerPrivate::overridableAction(Id id)
void ActionManagerPrivate::readUserSettings(Id id, Action *cmd) void ActionManagerPrivate::readUserSettings(Id id, Action *cmd)
{ {
// TODO Settings V2 were introduced in Qt Creator 4.13, remove old settings at some point
QSettings *settings = ICore::settings(); QSettings *settings = ICore::settings();
settings->beginGroup(QLatin1String(kKeyboardSettingsKey)); // transfer from old settings if not done before
if (settings->contains(id.toString())) const QString group = settings->childGroups().contains(kKeyboardSettingsKeyV2)
cmd->setKeySequence(QKeySequence(settings->value(id.toString()).toString())); ? QString(kKeyboardSettingsKeyV2)
: QString(kKeyboardSettingsKey);
settings->beginGroup(group);
if (settings->contains(id.toString())) {
const QVariant v = settings->value(id.toString());
if (QMetaType::Type(v.type()) == QMetaType::QStringList) {
cmd->setKeySequences(Utils::transform<QList>(v.toStringList(), [](const QString &s) {
return QKeySequence::fromString(s);
}));
} else {
cmd->setKeySequences({QKeySequence::fromString(v.toString())});
}
}
settings->endGroup(); settings->endGroup();
} }
void ActionManagerPrivate::saveSettings(Action *cmd) void ActionManagerPrivate::saveSettings(Action *cmd)
{ {
const QString settingsKey = QLatin1String(kKeyboardSettingsKey) + QLatin1Char('/') const QString id = cmd->id().toString();
+ cmd->id().toString(); const QString settingsKey = QLatin1String(kKeyboardSettingsKeyV2) + '/' + id;
QKeySequence key = cmd->keySequence(); const QString compatSettingsKey = QLatin1String(kKeyboardSettingsKey) + '/' + id;
if (key != cmd->defaultKeySequence()) const QList<QKeySequence> keys = cmd->keySequences();
ICore::settings()->setValue(settingsKey, key.toString()); const QList<QKeySequence> defaultKeys = cmd->defaultKeySequences();
else if (keys != defaultKeys) {
if (keys.isEmpty()) {
ICore::settings()->setValue(settingsKey, QString());
ICore::settings()->setValue(compatSettingsKey, QString());
} else if (keys.size() == 1) {
ICore::settings()->setValue(settingsKey, keys.first().toString());
ICore::settings()->setValue(compatSettingsKey, keys.first().toString());
} else {
ICore::settings()->setValue(settingsKey,
Utils::transform<QStringList>(keys,
[](const QKeySequence &k) {
return k.toString();
}));
}
} else {
ICore::settings()->remove(settingsKey); ICore::settings()->remove(settingsKey);
}
} }
void ActionManagerPrivate::saveSettings() void ActionManagerPrivate::saveSettings()

View File

@@ -253,13 +253,20 @@ Id Action::id() const
void Action::setDefaultKeySequence(const QKeySequence &key) void Action::setDefaultKeySequence(const QKeySequence &key)
{ {
if (!m_isKeyInitialized) if (!m_isKeyInitialized)
setKeySequence(key); setKeySequences({key});
m_defaultKey = key; m_defaultKeys = {key};
} }
QKeySequence Action::defaultKeySequence() const void Action::setDefaultKeySequences(const QList<QKeySequence> &keys)
{ {
return m_defaultKey; if (!m_isKeyInitialized)
setKeySequences(keys);
m_defaultKeys = keys;
}
QList<QKeySequence> Action::defaultKeySequences() const
{
return m_defaultKeys;
} }
QAction *Action::action() const QAction *Action::action() const
@@ -277,13 +284,18 @@ Context Action::context() const
return m_context; return m_context;
} }
void Action::setKeySequence(const QKeySequence &key) void Action::setKeySequences(const QList<QKeySequence> &keys)
{ {
m_isKeyInitialized = true; m_isKeyInitialized = true;
m_action->setShortcut(key); m_action->setShortcuts(keys);
emit keySequenceChanged(); emit keySequenceChanged();
} }
QList<QKeySequence> Action::keySequences() const
{
return m_action->shortcuts();
}
QKeySequence Action::keySequence() const QKeySequence Action::keySequence() const
{ {
return m_action->shortcut(); return m_action->shortcut();

View File

@@ -58,7 +58,9 @@ public:
Q_DECLARE_FLAGS(CommandAttributes, CommandAttribute) Q_DECLARE_FLAGS(CommandAttributes, CommandAttribute)
virtual void setDefaultKeySequence(const QKeySequence &key) = 0; virtual void setDefaultKeySequence(const QKeySequence &key) = 0;
virtual QKeySequence defaultKeySequence() const = 0; virtual void setDefaultKeySequences(const QList<QKeySequence> &keys) = 0;
virtual QList<QKeySequence> defaultKeySequences() const = 0;
virtual QList<QKeySequence> keySequences() const = 0;
virtual QKeySequence keySequence() const = 0; virtual QKeySequence keySequence() const = 0;
// explicitly set the description (used e.g. in shortcut settings) // explicitly set the description (used e.g. in shortcut settings)
// default is to use the action text for actions, or the whatsThis for shortcuts, // default is to use the action text for actions, or the whatsThis for shortcuts,
@@ -78,7 +80,7 @@ public:
virtual bool isActive() const = 0; virtual bool isActive() const = 0;
virtual void setKeySequence(const QKeySequence &key) = 0; virtual void setKeySequences(const QList<QKeySequence> &keys) = 0;
virtual QString stringWithAppendedShortcut(const QString &str) const = 0; virtual QString stringWithAppendedShortcut(const QString &str) const = 0;
void augmentActionWithShortcutToolTip(QAction *action) const; void augmentActionWithShortcutToolTip(QAction *action) const;
static QToolButton *toolButtonWithAppendedShortcut(QAction *action, Command *cmd); static QToolButton *toolButtonWithAppendedShortcut(QAction *action, Command *cmd);

View File

@@ -52,9 +52,11 @@ public:
Id id() const override; Id id() const override;
void setDefaultKeySequence(const QKeySequence &key) override; void setDefaultKeySequence(const QKeySequence &key) override;
QKeySequence defaultKeySequence() const override; void setDefaultKeySequences(const QList<QKeySequence> &key) override;
QList<QKeySequence> defaultKeySequences() const override;
void setKeySequence(const QKeySequence &key) override; void setKeySequences(const QList<QKeySequence> &keys) override;
QList<QKeySequence> keySequences() const override;
QKeySequence keySequence() const override; QKeySequence keySequence() const override;
void setDescription(const QString &text) override; void setDescription(const QString &text) override;
@@ -92,7 +94,7 @@ private:
Context m_context; Context m_context;
CommandAttributes m_attributes; CommandAttributes m_attributes;
Id m_id; Id m_id;
QKeySequence m_defaultKey; QList<QKeySequence> m_defaultKeys;
QString m_defaultText; QString m_defaultText;
QString m_touchBarText; QString m_touchBarText;
QIcon m_touchBarIcon; QIcon m_touchBarIcon;

View File

@@ -83,9 +83,9 @@ CommandsFile::CommandsFile(const QString &filename)
/*! /*!
\internal \internal
*/ */
QMap<QString, QKeySequence> CommandsFile::importCommands() const QMap<QString, QList<QKeySequence>> CommandsFile::importCommands() const
{ {
QMap<QString, QKeySequence> result; QMap<QString, QList<QKeySequence>> result;
QFile file(m_filename); QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
@@ -101,19 +101,17 @@ QMap<QString, QKeySequence> CommandsFile::importCommands() const
case QXmlStreamReader::StartElement: { case QXmlStreamReader::StartElement: {
const QStringRef name = r.name(); const QStringRef name = r.name();
if (name == ctx.shortCutElement) { if (name == ctx.shortCutElement) {
if (!currentId.isEmpty()) // shortcut element without key element == empty shortcut
result.insert(currentId, QKeySequence());
currentId = r.attributes().value(ctx.idAttribute).toString(); currentId = r.attributes().value(ctx.idAttribute).toString();
if (!result.contains(currentId))
result.insert(currentId, {});
} else if (name == ctx.keyElement) { } else if (name == ctx.keyElement) {
QTC_ASSERT(!currentId.isEmpty(), return result); QTC_ASSERT(!currentId.isEmpty(), continue);
const QXmlStreamAttributes attributes = r.attributes(); const QXmlStreamAttributes attributes = r.attributes();
if (attributes.hasAttribute(ctx.valueAttribute)) { if (attributes.hasAttribute(ctx.valueAttribute)) {
const QString keyString = attributes.value(ctx.valueAttribute).toString(); const QString keyString = attributes.value(ctx.valueAttribute).toString();
result.insert(currentId, QKeySequence(keyString)); QList<QKeySequence> keys = result.value(currentId);
} else { result.insert(currentId, keys << QKeySequence(keyString));
result.insert(currentId, QKeySequence());
} }
currentId.clear();
} // if key element } // if key element
} // case QXmlStreamReader::StartElement } // case QXmlStreamReader::StartElement
default: default:
@@ -144,14 +142,16 @@ bool CommandsFile::exportCommands(const QList<ShortcutItem *> &items)
w.writeStartElement(ctx.mappingElement); w.writeStartElement(ctx.mappingElement);
foreach (const ShortcutItem *item, items) { foreach (const ShortcutItem *item, items) {
const Id id = item->m_cmd->id(); const Id id = item->m_cmd->id();
if (item->m_key.isEmpty()) { if (item->m_keys.isEmpty() || item->m_keys.first().isEmpty()) {
w.writeEmptyElement(ctx.shortCutElement); w.writeEmptyElement(ctx.shortCutElement);
w.writeAttribute(ctx.idAttribute, id.toString()); w.writeAttribute(ctx.idAttribute, id.toString());
} else { } else {
w.writeStartElement(ctx.shortCutElement); w.writeStartElement(ctx.shortCutElement);
w.writeAttribute(ctx.idAttribute, id.toString()); w.writeAttribute(ctx.idAttribute, id.toString());
w.writeEmptyElement(ctx.keyElement); for (const QKeySequence &k : item->m_keys) {
w.writeAttribute(ctx.valueAttribute, item->m_key.toString()); w.writeEmptyElement(ctx.keyElement);
w.writeAttribute(ctx.valueAttribute, k.toString());
}
w.writeEndElement(); // Shortcut w.writeEndElement(); // Shortcut
} }
} }

View File

@@ -43,7 +43,7 @@ class CommandsFile : public QObject
public: public:
CommandsFile(const QString &filename); CommandsFile(const QString &filename);
QMap<QString, QKeySequence> importCommands() const; QMap<QString, QList<QKeySequence> > importCommands() const;
bool exportCommands(const QList<ShortcutItem *> &items); bool exportCommands(const QList<ShortcutItem *> &items);
private: private:

View File

@@ -33,6 +33,7 @@
#include <coreplugin/actionmanager/command_p.h> #include <coreplugin/actionmanager/command_p.h>
#include <coreplugin/actionmanager/commandsfile.h> #include <coreplugin/actionmanager/commandsfile.h>
#include <utils/algorithm.h>
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -50,6 +51,8 @@
Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*) Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*)
const char kSeparator[] = " | ";
static int translateModifiers(Qt::KeyboardModifiers state, static int translateModifiers(Qt::KeyboardModifiers state,
const QString &text) const QString &text)
{ {
@@ -82,9 +85,23 @@ static QString keySequenceToEditString(const QKeySequence &sequence)
return text; return text;
} }
static QString keySequencesToEditString(const QList<QKeySequence> &sequence)
{
return Utils::transform(sequence, keySequenceToEditString).join(kSeparator);
}
static QString keySequencesToNativeString(const QList<QKeySequence> &sequence)
{
return Utils::transform(sequence,
[](const QKeySequence &k) {
return k.toString(QKeySequence::NativeText);
})
.join(kSeparator);
}
static QKeySequence keySequenceFromEditString(const QString &editString) static QKeySequence keySequenceFromEditString(const QString &editString)
{ {
QString text = editString; QString text = editString.trimmed();
if (Utils::HostOsInfo::isMacHost()) { if (Utils::HostOsInfo::isMacHost()) {
// adapt the modifier names // adapt the modifier names
text.replace(QLatin1String("Opt"), QLatin1String("Alt"), Qt::CaseInsensitive); text.replace(QLatin1String("Opt"), QLatin1String("Alt"), Qt::CaseInsensitive);
@@ -94,6 +111,21 @@ static QKeySequence keySequenceFromEditString(const QString &editString)
return QKeySequence::fromString(text, QKeySequence::PortableText); return QKeySequence::fromString(text, QKeySequence::PortableText);
} }
struct ParsedKey
{
QString text;
QKeySequence key;
};
static QList<ParsedKey> keySequencesFromEditString(const QString &editString)
{
if (editString.trimmed().isEmpty())
return {};
return Utils::transform(editString.split(kSeparator), [](const QString &str) {
return ParsedKey{str, keySequenceFromEditString(str)};
});
}
static bool keySequenceIsValid(const QKeySequence &sequence) static bool keySequenceIsValid(const QKeySequence &sequence)
{ {
if (sequence.isEmpty()) if (sequence.isEmpty())
@@ -242,7 +274,7 @@ private:
void resetToDefault(); void resetToDefault();
bool validateShortcutEdit() const; bool validateShortcutEdit() const;
bool markCollisions(ShortcutItem *); bool markCollisions(ShortcutItem *);
void setKeySequence(const QKeySequence &key); void setKeySequences(const QList<QKeySequence> &keys);
void showConflicts(); void showConflicts();
void clear(); void clear();
@@ -292,8 +324,10 @@ ShortcutSettingsWidget::ShortcutSettingsWidget()
"enter \"Ctrl+Shift+Escape,A\".") "enter \"Ctrl+Shift+Escape,A\".")
+ QLatin1String("</body></html>")); + QLatin1String("</body></html>"));
auto shortcutButton = new ShortcutButton(m_shortcutBox); auto shortcutButton = new ShortcutButton(m_shortcutBox);
connect(shortcutButton, &ShortcutButton::keySequenceChanged, connect(shortcutButton,
this, &ShortcutSettingsWidget::setKeySequence); &ShortcutButton::keySequenceChanged,
this,
[this](const QKeySequence &k) { setKeySequences({k}); });
auto resetButton = new QPushButton(tr("Reset"), m_shortcutBox); auto resetButton = new QPushButton(tr("Reset"), m_shortcutBox);
resetButton->setToolTip(tr("Reset to default.")); resetButton->setToolTip(tr("Reset to default."));
connect(resetButton, &QPushButton::clicked, connect(resetButton, &QPushButton::clicked,
@@ -343,7 +377,7 @@ QWidget *ShortcutSettings::widget()
void ShortcutSettingsWidget::apply() void ShortcutSettingsWidget::apply()
{ {
foreach (ShortcutItem *item, m_scitems) foreach (ShortcutItem *item, m_scitems)
item->m_cmd->setKeySequence(item->m_key); item->m_cmd->setKeySequences(item->m_keys);
} }
void ShortcutSettings::apply() void ShortcutSettings::apply()
@@ -372,12 +406,32 @@ void ShortcutSettingsWidget::handleCurrentCommandChanged(QTreeWidgetItem *curren
m_warningLabel->clear(); m_warningLabel->clear();
m_shortcutBox->setEnabled(false); m_shortcutBox->setEnabled(false);
} else { } else {
setKeySequence(scitem->m_key); setKeySequences(scitem->m_keys);
markCollisions(scitem); markCollisions(scitem);
m_shortcutBox->setEnabled(true); m_shortcutBox->setEnabled(true);
} }
} }
static bool checkValidity(const QList<ParsedKey> &keys, QString *warningMessage)
{
QTC_ASSERT(warningMessage, return true);
for (const ParsedKey &k : keys) {
if (!keySequenceIsValid(k.key)) {
*warningMessage = ShortcutSettingsWidget::tr("Invalid key sequence \"%1\".").arg(k.text);
return false;
}
}
for (const ParsedKey &k : keys) {
if (textKeySequence(k.key)) {
*warningMessage = ShortcutSettingsWidget::tr(
"Key sequence \"%1\" will not work in editor.")
.arg(k.text);
break;
}
}
return true;
}
bool ShortcutSettingsWidget::validateShortcutEdit() const bool ShortcutSettingsWidget::validateShortcutEdit() const
{ {
m_warningLabel->clear(); m_warningLabel->clear();
@@ -385,43 +439,57 @@ bool ShortcutSettingsWidget::validateShortcutEdit() const
ShortcutItem *item = shortcutItem(current); ShortcutItem *item = shortcutItem(current);
if (!item) if (!item)
return true; return true;
bool valid = false;
const QString text = m_shortcutEdit->text().trimmed(); const QString text = m_shortcutEdit->text().trimmed();
const QKeySequence currentKey = keySequenceFromEditString(text); const QList<ParsedKey> currentKeys = keySequencesFromEditString(text);
QString warningMessage;
bool isValid = checkValidity(currentKeys, &warningMessage);
if (keySequenceIsValid(currentKey) || text.isEmpty()) { if (isValid) {
item->m_key = currentKey; item->m_keys = Utils::transform(currentKeys, &ParsedKey::key);
auto that = const_cast<ShortcutSettingsWidget *>(this); auto that = const_cast<ShortcutSettingsWidget *>(this);
if (item->m_cmd->defaultKeySequence() != item->m_key) if (item->m_keys != item->m_cmd->defaultKeySequences())
that->setModified(current, true); that->setModified(current, true);
else else
that->setModified(current, false); that->setModified(current, false);
current->setText(2, item->m_key.toString(QKeySequence::NativeText)); current->setText(2, keySequencesToNativeString(item->m_keys));
valid = !that->markCollisions(item); isValid = !that->markCollisions(item);
if (!valid) { if (!isValid) {
m_warningLabel->setText( m_warningLabel->setText(
tr("Key sequence has potential conflicts. <a href=\"#conflicts\">Show.</a>")); tr("Key sequence has potential conflicts. <a href=\"#conflicts\">Show.</a>"));
} else if (textKeySequence(currentKey)) {
m_warningLabel->setText(tr("Key sequence will not work in editor."));
} }
} else {
m_warningLabel->setText(m_warningLabel->text() + tr("Invalid key sequence."));
} }
return valid; if (!warningMessage.isEmpty()) {
if (m_warningLabel->text().isEmpty())
m_warningLabel->setText(warningMessage);
else
m_warningLabel->setText(m_warningLabel->text() + " " + warningMessage);
}
return isValid;
} }
bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidgetItem *item, bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidgetItem *item,
int column) const int column) const
{ {
QString text; const ShortcutItem *scitem = shortcutItem(item);
ShortcutItem *scitem = shortcutItem(item);
if (column == item->columnCount() - 1) { // shortcut if (column == item->columnCount() - 1) { // shortcut
// filter on the shortcut edit text // filter on the shortcut edit text
if (!scitem) if (!scitem)
return true; return true;
text = keySequenceToEditString(scitem->m_key); const QStringList filters = Utils::transform(filterString.split(kSeparator),
} else if (column == 0 && scitem) { // command id [](const QString &s) { return s.trimmed(); });
for (const QKeySequence &k : scitem->m_keys) {
const QString &keyString = keySequenceToEditString(k);
const bool found = Utils::anyOf(filters, [keyString](const QString &f) {
return keyString.contains(f, Qt::CaseInsensitive);
});
if (found)
return false;
}
return true;
}
QString text;
if (column == 0 && scitem) { // command id
text = scitem->m_cmd->id().toString(); text = scitem->m_cmd->id().toString();
} else { } else {
text = item->text(column); text = item->text(column);
@@ -429,9 +497,9 @@ bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidg
return !text.contains(filterString, Qt::CaseInsensitive); return !text.contains(filterString, Qt::CaseInsensitive);
} }
void ShortcutSettingsWidget::setKeySequence(const QKeySequence &key) void ShortcutSettingsWidget::setKeySequences(const QList<QKeySequence> &keys)
{ {
m_shortcutEdit->setText(keySequenceToEditString(key)); m_shortcutEdit->setText(keySequencesToEditString(keys));
} }
void ShortcutSettingsWidget::showConflicts() void ShortcutSettingsWidget::showConflicts()
@@ -439,7 +507,7 @@ void ShortcutSettingsWidget::showConflicts()
QTreeWidgetItem *current = commandList()->currentItem(); QTreeWidgetItem *current = commandList()->currentItem();
ShortcutItem *scitem = shortcutItem(current); ShortcutItem *scitem = shortcutItem(current);
if (scitem) if (scitem)
setFilterText(keySequenceToEditString(scitem->m_key)); setFilterText(keySequencesToEditString(scitem->m_keys));
} }
void ShortcutSettingsWidget::resetToDefault() void ShortcutSettingsWidget::resetToDefault()
@@ -447,7 +515,7 @@ void ShortcutSettingsWidget::resetToDefault()
QTreeWidgetItem *current = commandList()->currentItem(); QTreeWidgetItem *current = commandList()->currentItem();
ShortcutItem *scitem = shortcutItem(current); ShortcutItem *scitem = shortcutItem(current);
if (scitem) { if (scitem) {
setKeySequence(scitem->m_cmd->defaultKeySequence()); setKeySequences(scitem->m_cmd->defaultKeySequences());
foreach (ShortcutItem *item, m_scitems) foreach (ShortcutItem *item, m_scitems)
markCollisions(item); markCollisions(item);
} }
@@ -461,17 +529,16 @@ void ShortcutSettingsWidget::importAction()
if (!fileName.isEmpty()) { if (!fileName.isEmpty()) {
CommandsFile cf(fileName); CommandsFile cf(fileName);
QMap<QString, QKeySequence> mapping = cf.importCommands(); QMap<QString, QList<QKeySequence>> mapping = cf.importCommands();
for (ShortcutItem *item : qAsConst(m_scitems)) {
foreach (ShortcutItem *item, m_scitems) {
QString sid = item->m_cmd->id().toString(); QString sid = item->m_cmd->id().toString();
if (mapping.contains(sid)) { if (mapping.contains(sid)) {
item->m_key = mapping.value(sid); item->m_keys = mapping.value(sid);
item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText)); item->m_item->setText(2, keySequencesToNativeString(item->m_keys));
if (item->m_item == commandList()->currentItem()) if (item->m_item == commandList()->currentItem())
emit currentCommandChanged(item->m_item); emit currentCommandChanged(item->m_item);
if (item->m_cmd->defaultKeySequence() != item->m_key) if (item->m_keys != item->m_cmd->defaultKeySequences())
setModified(item->m_item, true); setModified(item->m_item, true);
else else
setModified(item->m_item, false); setModified(item->m_item, false);
@@ -486,8 +553,8 @@ void ShortcutSettingsWidget::importAction()
void ShortcutSettingsWidget::defaultAction() void ShortcutSettingsWidget::defaultAction()
{ {
foreach (ShortcutItem *item, m_scitems) { foreach (ShortcutItem *item, m_scitems) {
item->m_key = item->m_cmd->defaultKeySequence(); item->m_keys = item->m_cmd->defaultKeySequences();
item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText)); item->m_item->setText(2, keySequencesToNativeString(item->m_keys));
setModified(item->m_item, false); setModified(item->m_item, false);
if (item->m_item == commandList()->currentItem()) if (item->m_item == commandList()->currentItem())
emit currentCommandChanged(item->m_item); emit currentCommandChanged(item->m_item);
@@ -551,11 +618,11 @@ void ShortcutSettingsWidget::initialize()
} }
sections[section]->addChild(item); sections[section]->addChild(item);
s->m_key = c->keySequence(); s->m_keys = c->keySequences();
item->setText(0, subId); item->setText(0, subId);
item->setText(1, c->description()); item->setText(1, c->description());
item->setText(2, s->m_key.toString(QKeySequence::NativeText)); item->setText(2, keySequencesToNativeString(s->m_keys));
if (s->m_cmd->defaultKeySequence() != s->m_key) if (s->m_keys != s->m_cmd->defaultKeySequences())
setModified(item, true); setModified(item, true);
item->setData(0, Qt::UserRole, QVariant::fromValue(s)); item->setData(0, Qt::UserRole, QVariant::fromValue(s));
@@ -568,35 +635,42 @@ void ShortcutSettingsWidget::initialize()
bool ShortcutSettingsWidget::markCollisions(ShortcutItem *item) bool ShortcutSettingsWidget::markCollisions(ShortcutItem *item)
{ {
bool hasCollision = false; bool hasCollision = false;
if (!item->m_key.isEmpty()) { if (!item->m_keys.isEmpty()) {
Id globalId(Constants::C_GLOBAL); Id globalId(Constants::C_GLOBAL);
const Context itemContext = item->m_cmd->context(); const Context itemContext = item->m_cmd->context();
const bool itemHasGlobalContext = itemContext.contains(globalId); const bool itemHasGlobalContext = itemContext.contains(globalId);
foreach (ShortcutItem *currentItem, m_scitems) { for (ShortcutItem *currentItem : qAsConst(m_scitems)) {
if (currentItem->m_key.isEmpty() || item == currentItem if (item == currentItem)
|| item->m_key != currentItem->m_key)
continue; continue;
const bool containsSameShortcut = Utils::anyOf(currentItem->m_keys,
[item](const QKeySequence &k) {
return item->m_keys.contains(k);
});
if (!containsSameShortcut)
continue;
// check if contexts might conflict
const Context currentContext = currentItem->m_cmd->context(); const Context currentContext = currentItem->m_cmd->context();
bool currentIsConflicting = (itemHasGlobalContext && !currentContext.isEmpty()); bool currentIsConflicting = (itemHasGlobalContext && !currentContext.isEmpty());
if (!currentIsConflicting) { if (!currentIsConflicting) {
foreach (const Id &id, currentContext) { for (const Id &id : currentContext) {
if ((id == globalId && !itemContext.isEmpty()) if ((id == globalId && !itemContext.isEmpty()) || itemContext.contains(id)) {
|| itemContext.contains(id)) {
currentIsConflicting = true; currentIsConflicting = true;
break; break;
} }
} }
} }
if (currentIsConflicting) { if (currentIsConflicting) {
currentItem->m_item->setForeground( currentItem->m_item->setForeground(2,
2, Utils::creatorTheme()->color(Utils::Theme::TextColorError)); Utils::creatorTheme()->color(
Utils::Theme::TextColorError));
hasCollision = true; hasCollision = true;
} }
} }
} }
item->m_item->setForeground(2, hasCollision item->m_item->setForeground(2,
? Utils::creatorTheme()->color(Utils::Theme::TextColorError) hasCollision
: commandList()->palette().windowText()); ? Utils::creatorTheme()->color(Utils::Theme::TextColorError)
: commandList()->palette().windowText());
return hasCollision; return hasCollision;
} }

View File

@@ -51,7 +51,7 @@ class ShortcutSettingsWidget;
struct ShortcutItem struct ShortcutItem
{ {
Command *m_cmd; Command *m_cmd;
QKeySequence m_key; QList<QKeySequence> m_keys;
QTreeWidgetItem *m_item; QTreeWidgetItem *m_item;
}; };