Improve keyboard shortcut settings

- change the line edit to accept actual text input in a form similar to
  QKeySequence::fromString (with special "native" form on OS X)
- add a button that allows entering a key sequence by pressing keys,
  including support for e.g. escape key, which was broken before because
  it closed the dialog
- add a warning label, that allows filtering the list for all
  potentially conflicting shortcuts

Task-number: QTCREATORBUG-6
Change-Id: I94fc63525f653127e87f6ef2bffe72d8dcaa867d
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@theqtcompany.com>
This commit is contained in:
Eike Ziller
2015-04-29 18:01:21 +02:00
parent 0bd0468263
commit 25057a7acc
5 changed files with 367 additions and 277 deletions

View File

@@ -82,24 +82,6 @@ public:
importButton = new QPushButton(CommandMappings::tr("Import..."), groupBox); importButton = new QPushButton(CommandMappings::tr("Import..."), groupBox);
exportButton = new QPushButton(CommandMappings::tr("Export..."), groupBox); exportButton = new QPushButton(CommandMappings::tr("Export..."), groupBox);
targetEditGroup = new QGroupBox(CommandMappings::tr("Target Identifier"), parent);
targetEditGroup->setEnabled(false);
targetEdit = new FancyLineEdit(targetEditGroup);
targetEdit->setAutoHideButton(FancyLineEdit::Right, true);
targetEdit->setPlaceholderText(QString());
targetEdit->setFiltering(true);
targetEdit->setValidationFunction([this](FancyLineEdit *, QString *) {
return !q->hasConflicts();
});
resetButton = new QPushButton(targetEditGroup);
resetButton->setToolTip(CommandMappings::tr("Reset to default."));
resetButton->setText(CommandMappings::tr("Reset"));
QLabel *infoLabel = new QLabel(targetEditGroup);
infoLabel->setTextFormat(Qt::RichText);
QHBoxLayout *hboxLayout1 = new QHBoxLayout(); QHBoxLayout *hboxLayout1 = new QHBoxLayout();
hboxLayout1->addWidget(defaultButton); hboxLayout1->addWidget(defaultButton);
hboxLayout1->addStretch(); hboxLayout1->addStretch();
@@ -114,25 +96,9 @@ public:
vboxLayout1->addWidget(commandList); vboxLayout1->addWidget(commandList);
vboxLayout1->addLayout(hboxLayout1); vboxLayout1->addLayout(hboxLayout1);
targetLabel = new QLabel(CommandMappings::tr("Target:"));
QHBoxLayout *hboxLayout2 = new QHBoxLayout();
hboxLayout2->addWidget(targetLabel);
hboxLayout2->addWidget(targetEdit);
hboxLayout2->addWidget(resetButton);
QVBoxLayout *vboxLayout2 = new QVBoxLayout(targetEditGroup);
vboxLayout2->addLayout(hboxLayout2);
vboxLayout2->addWidget(infoLabel);
QVBoxLayout *vboxLayout = new QVBoxLayout(parent); QVBoxLayout *vboxLayout = new QVBoxLayout(parent);
vboxLayout->addWidget(groupBox); vboxLayout->addWidget(groupBox);
vboxLayout->addWidget(targetEditGroup);
q->connect(targetEdit, &FancyLineEdit::buttonClicked,
q, &CommandMappings::removeTargetIdentifier);
q->connect(resetButton, &QPushButton::clicked,
q, &CommandMappings::resetTargetIdentifier);
q->connect(exportButton, &QPushButton::clicked, q->connect(exportButton, &QPushButton::clicked,
q, &CommandMappings::exportAction); q, &CommandMappings::exportAction);
q->connect(importButton, &QPushButton::clicked, q->connect(importButton, &QPushButton::clicked,
@@ -145,9 +111,7 @@ public:
q->connect(filterEdit, &FancyLineEdit::textChanged, q->connect(filterEdit, &FancyLineEdit::textChanged,
q, &CommandMappings::filterChanged); q, &CommandMappings::filterChanged);
q->connect(commandList, &QTreeWidget::currentItemChanged, q->connect(commandList, &QTreeWidget::currentItemChanged,
q, &CommandMappings::commandChanged); q, &CommandMappings::currentCommandChanged);
q->connect(targetEdit, &FancyLineEdit::textChanged,
q, &CommandMappings::targetIdentifierChanged);
new HeaderViewStretcher(commandList->header(), 1); new HeaderViewStretcher(commandList->header(), 1);
} }
@@ -160,10 +124,6 @@ public:
QPushButton *defaultButton; QPushButton *defaultButton;
QPushButton *importButton; QPushButton *importButton;
QPushButton *exportButton; QPushButton *exportButton;
QGroupBox *targetEditGroup;
QLabel *targetLabel;
FancyLineEdit *targetEdit;
QPushButton *resetButton;
}; };
} // namespace Internal } // namespace Internal
@@ -189,41 +149,16 @@ QTreeWidget *CommandMappings::commandList() const
return d->commandList; return d->commandList;
} }
QLineEdit *CommandMappings::targetEdit() const
{
return d->targetEdit;
}
void CommandMappings::setPageTitle(const QString &s) void CommandMappings::setPageTitle(const QString &s)
{ {
d->groupBox->setTitle(s); d->groupBox->setTitle(s);
} }
void CommandMappings::setTargetLabelText(const QString &s)
{
d->targetLabel->setText(s);
}
void CommandMappings::setTargetEditTitle(const QString &s)
{
d->targetEditGroup->setTitle(s);
}
void CommandMappings::setTargetHeader(const QString &s) void CommandMappings::setTargetHeader(const QString &s)
{ {
d->commandList->setHeaderLabels(QStringList() << tr("Command") << tr("Label") << s); d->commandList->setHeaderLabels(QStringList() << tr("Command") << tr("Label") << s);
} }
void CommandMappings::commandChanged(QTreeWidgetItem *current)
{
if (!current || !current->data(0, Qt::UserRole).isValid()) {
d->targetEdit->clear();
d->targetEditGroup->setEnabled(false);
return;
}
d->targetEditGroup->setEnabled(true);
}
void CommandMappings::filterChanged(const QString &f) void CommandMappings::filterChanged(const QString &f)
{ {
for (int i = 0; i < d->commandList->topLevelItemCount(); ++i) { for (int i = 0; i < d->commandList->topLevelItemCount(); ++i) {
@@ -232,11 +167,6 @@ void CommandMappings::filterChanged(const QString &f)
} }
} }
bool CommandMappings::hasConflicts() const
{
return true;
}
bool CommandMappings::filter(const QString &filterString, QTreeWidgetItem *item) bool CommandMappings::filter(const QString &filterString, QTreeWidgetItem *item)
{ {
bool visible = filterString.isEmpty(); bool visible = filterString.isEmpty();
@@ -275,7 +205,12 @@ void CommandMappings::setModified(QTreeWidgetItem *item , bool modified)
QString CommandMappings::filterText() const QString CommandMappings::filterText() const
{ {
return d->filterEdit ? d->filterEdit->text() : QString(); return d->filterEdit->text();
}
void CommandMappings::setFilterText(const QString &text)
{
d->filterEdit->setText(text);
} }
} // namespace Core } // namespace Core

View File

@@ -54,15 +54,11 @@ class CORE_EXPORT CommandMappings : public QWidget
public: public:
CommandMappings(QWidget *parent = 0); CommandMappings(QWidget *parent = 0);
~CommandMappings(); ~CommandMappings();
virtual bool hasConflicts() const;
protected slots: signals:
void currentCommandChanged(QTreeWidgetItem *current);
protected: protected:
virtual void removeTargetIdentifier() = 0;
virtual void resetTargetIdentifier() = 0;
virtual void targetIdentifierChanged() = 0;
virtual void defaultAction() = 0; virtual void defaultAction() = 0;
virtual void exportAction() {} virtual void exportAction() {}
@@ -72,16 +68,12 @@ protected:
void filterChanged(const QString &f); void filterChanged(const QString &f);
virtual void commandChanged(QTreeWidgetItem *current);
// access to m_page // access to m_page
void setImportExportEnabled(bool enabled); void setImportExportEnabled(bool enabled);
QTreeWidget *commandList() const; QTreeWidget *commandList() const;
QLineEdit *targetEdit() const;
QString filterText() const; QString filterText() const;
void setFilterText(const QString &text);
void setPageTitle(const QString &s); void setPageTitle(const QString &s);
void setTargetLabelText(const QString &s);
void setTargetEditTitle(const QString &s);
void setTargetHeader(const QString &s); void setTargetHeader(const QString &s);
void setModified(QTreeWidgetItem *item, bool modified); void setModified(QTreeWidgetItem *item, bool modified);

View File

@@ -41,12 +41,16 @@
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/theme/theme.h>
#include <QKeyEvent> #include <QKeyEvent>
#include <QFileDialog> #include <QFileDialog>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <QCoreApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*) Q_DECLARE_METATYPE(Core::Internal::ShortcutItem*)
@@ -71,23 +75,208 @@ static int translateModifiers(Qt::KeyboardModifiers state,
return result; return result;
} }
static QString keySequenceToEditString(const QKeySequence &sequence)
{
QString text = sequence.toString(QKeySequence::PortableText);
if (Utils::HostOsInfo::isMacHost()) {
// adapt the modifier names
text.replace(QLatin1String("Ctrl"), QLatin1String("Cmd"), Qt::CaseInsensitive);
text.replace(QLatin1String("Alt"), QLatin1String("Opt"), Qt::CaseInsensitive);
text.replace(QLatin1String("Meta"), QLatin1String("Ctrl"), Qt::CaseInsensitive);
}
return text;
}
static QKeySequence keySequenceFromEditString(const QString &editString)
{
QString text = editString;
if (Utils::HostOsInfo::isMacHost()) {
// adapt the modifier names
text.replace(QLatin1String("Opt"), QLatin1String("Alt"), Qt::CaseInsensitive);
text.replace(QLatin1String("Ctrl"), QLatin1String("Meta"), Qt::CaseInsensitive);
text.replace(QLatin1String("Cmd"), QLatin1String("Ctrl"), Qt::CaseInsensitive);
}
return QKeySequence::fromString(text, QKeySequence::PortableText);
}
static bool keySequenceIsValid(const QKeySequence &sequence)
{
if (sequence.isEmpty())
return false;
for (int i = 0; i < sequence.count(); ++i) {
if (sequence[i] == Qt::Key_unknown)
return false;
}
return true;
}
namespace Core { namespace Core {
namespace Internal { namespace Internal {
ShortcutButton::ShortcutButton(QWidget *parent)
: QPushButton(parent)
{
setToolTip(tr("Click and type the new key sequence."));
setCheckable(true);
m_checkedText = tr("Stop Recording");
m_uncheckedText = tr("Record");
updateText();
connect(this, &ShortcutButton::toggled, this, &ShortcutButton::handleToggleChange);
}
QSize ShortcutButton::sizeHint() const
{
if (m_preferredWidth < 0) { // initialize size hint
const QString originalText = text();
ShortcutButton *that = const_cast<ShortcutButton *>(this);
that->setText(m_checkedText);
m_preferredWidth = QPushButton::sizeHint().width();
that->setText(m_uncheckedText);
int otherWidth = QPushButton::sizeHint().width();
if (otherWidth > m_preferredWidth)
m_preferredWidth = otherWidth;
that->setText(originalText);
}
return QSize(m_preferredWidth, QPushButton::sizeHint().height());
}
bool ShortcutButton::eventFilter(QObject *obj, QEvent *evt)
{
if (evt->type() == QEvent::ShortcutOverride) {
evt->accept();
return true;
}
if (evt->type() == QEvent::KeyRelease
|| evt->type() == QEvent::Shortcut
|| evt->type() == QEvent::Close/*Escape tries to close dialog*/) {
return true;
}
if (evt->type() == QEvent::MouseButtonPress && isChecked()) {
setChecked(false);
return true;
}
if (evt->type() == QEvent::KeyPress) {
QKeyEvent *k = static_cast<QKeyEvent*>(evt);
int nextKey = k->key();
if (m_keyNum > 3
|| nextKey == Qt::Key_Control
|| nextKey == Qt::Key_Shift
|| nextKey == Qt::Key_Meta
|| nextKey == Qt::Key_Alt) {
return false;
}
nextKey |= translateModifiers(k->modifiers(), k->text());
switch (m_keyNum) {
case 0:
m_key[0] = nextKey;
break;
case 1:
m_key[1] = nextKey;
break;
case 2:
m_key[2] = nextKey;
break;
case 3:
m_key[3] = nextKey;
break;
default:
break;
}
m_keyNum++;
k->accept();
emit keySequenceChanged(QKeySequence(m_key[0], m_key[1], m_key[2], m_key[3]));
if (m_keyNum > 3)
setChecked(false);
return true;
}
return QPushButton::eventFilter(obj, evt);
}
void ShortcutButton::updateText()
{
setText(isChecked() ? m_checkedText : m_uncheckedText);
}
void ShortcutButton::handleToggleChange(bool toogleState)
{
updateText();
m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;
if (toogleState) {
if (qApp->focusWidget())
qApp->focusWidget()->clearFocus(); // funny things happen otherwise
qApp->installEventFilter(this);
} else {
qApp->removeEventFilter(this);
}
}
ShortcutSettingsWidget::ShortcutSettingsWidget(QWidget *parent) ShortcutSettingsWidget::ShortcutSettingsWidget(QWidget *parent)
: CommandMappings(parent) : CommandMappings(parent)
{ {
setPageTitle(tr("Keyboard Shortcuts")); setPageTitle(tr("Keyboard Shortcuts"));
setTargetLabelText(tr("Key sequence:"));
setTargetEditTitle(tr("Shortcut"));
setTargetHeader(tr("Shortcut")); setTargetHeader(tr("Shortcut"));
targetEdit()->setPlaceholderText(tr("Type to set shortcut"));
m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;
connect(ActionManager::instance(), &ActionManager::commandListChanged, connect(ActionManager::instance(), &ActionManager::commandListChanged,
this, &ShortcutSettingsWidget::initialize); this, &ShortcutSettingsWidget::initialize);
targetEdit()->installEventFilter(this); connect(this, &ShortcutSettingsWidget::currentCommandChanged,
this, &ShortcutSettingsWidget::handleCurrentCommandChanged);
m_shortcutBox = new QGroupBox(tr("Shortcut"), this);
m_shortcutBox->setEnabled(false);
auto vboxLayout = new QVBoxLayout(m_shortcutBox);
m_shortcutBox->setLayout(vboxLayout);
auto hboxLayout = new QHBoxLayout;
vboxLayout->addLayout(hboxLayout);
m_shortcutEdit = new Utils::FancyLineEdit(m_shortcutBox);
m_shortcutEdit->setFiltering(true);
m_shortcutEdit->setPlaceholderText(tr("Enter key sequence as text"));
m_shortcutEdit->setValidationFunction([this](Utils::FancyLineEdit *, QString *) {
return validateShortcutEdit();
});
auto shortcutLabel = new QLabel(tr("Key sequence:"));
shortcutLabel->setToolTip(Utils::HostOsInfo::isMacHost()
? QLatin1String("<html><body>")
+ tr("Use \"Cmd\", \"Opt\", \"Ctrl\", and \"Shift\" for modifier keys. "
"Use \"Escape\", \"Backspace\", \"Delete\", \"Insert\", \"Home\", and so on, for special keys. "
"Combine individual keys with \"+\", "
"and combine multiple shortcuts to a shortcut sequence with \",\". "
"For example, if the user must hold the Ctrl and Shift modifier keys "
"while pressing Escape, and then release and press A, "
"enter \"Ctrl+Shift+Escape,A\".")
+ QLatin1String("</body></html>")
: QLatin1String("<html><body>")
+ tr("Use \"Ctrl\", \"Alt\", \"Meta\", and \"Shift\" for modifier keys. "
"Use \"Escape\", \"Backspace\", \"Delete\", \"Insert\", \"Home\", and so on, for special keys. "
"Combine individual keys with \"+\", "
"and combine multiple shortcuts to a shortcut sequence with \",\". "
"For example, if the user must hold the Ctrl and Shift modifier keys "
"while pressing Escape, and then release and press A, "
"enter \"Ctrl+Shift+Escape,A\".")
+ QLatin1String("</body></html>"));
auto shortcutButton = new ShortcutButton(m_shortcutBox);
connect(shortcutButton, &ShortcutButton::keySequenceChanged,
this, &ShortcutSettingsWidget::setKeySequence);
auto resetButton = new QPushButton(tr("Reset"), m_shortcutBox);
resetButton->setToolTip(tr("Reset to default."));
connect(resetButton, &QPushButton::clicked,
this, &ShortcutSettingsWidget::resetToDefault);
hboxLayout->addWidget(shortcutLabel);
hboxLayout->addWidget(m_shortcutEdit);
hboxLayout->addWidget(shortcutButton);
hboxLayout->addWidget(resetButton);
m_warningLabel = new QLabel(m_shortcutBox);
m_warningLabel->setTextFormat(Qt::RichText);
QPalette palette = m_warningLabel->palette();
palette.setColor(QPalette::Active, QPalette::WindowText,
Utils::creatorTheme()->color(Utils::Theme::TextColorError));
m_warningLabel->setPalette(palette);
connect(m_warningLabel, &QLabel::linkActivated, this, &ShortcutSettingsWidget::showConflicts);
vboxLayout->addWidget(m_warningLabel);
layout()->addWidget(m_shortcutBox);
initialize(); initialize();
} }
@@ -130,122 +319,86 @@ void ShortcutSettings::finish()
delete m_widget; delete m_widget;
} }
bool ShortcutSettingsWidget::eventFilter(QObject *o, QEvent *e) void ShortcutSettingsWidget::handleCurrentCommandChanged(QTreeWidgetItem *current)
{ {
Q_UNUSED(o) if (!current || !current->data(0, Qt::UserRole).isValid()) {
m_shortcutEdit->clear();
if ( e->type() == QEvent::KeyPress ) { m_warningLabel->clear();
QKeyEvent *k = static_cast<QKeyEvent*>(e); m_shortcutBox->setEnabled(false);
handleKeyEvent(k);
return true;
}
if ( e->type() == QEvent::Shortcut ||
e->type() == QEvent::KeyRelease ) {
return true;
}
if (e->type() == QEvent::ShortcutOverride) {
// for shortcut overrides, we need to accept as well
e->accept();
return true;
}
return false;
}
void ShortcutSettingsWidget::commandChanged(QTreeWidgetItem *current)
{
CommandMappings::commandChanged(current);
if (!current || !current->data(0, Qt::UserRole).isValid())
return; return;
} else {
ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole)); ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
setKeySequence(scitem->m_key); setKeySequence(scitem->m_key);
markCollisions(scitem); markCollisions(scitem);
} m_shortcutBox->setEnabled(true);
void ShortcutSettingsWidget::targetIdentifierChanged()
{
QTreeWidgetItem *current = commandList()->currentItem();
if (current && current->data(0, Qt::UserRole).isValid()) {
ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
scitem->m_key = QKeySequence(m_key[0], m_key[1], m_key[2], m_key[3]);
if (scitem->m_cmd->defaultKeySequence() != scitem->m_key)
setModified(current, true);
else
setModified(current, false);
current->setText(2, scitem->m_key.toString(QKeySequence::NativeText));
markCollisions(scitem);
} }
} }
bool ShortcutSettingsWidget::hasConflicts() const bool ShortcutSettingsWidget::validateShortcutEdit() const
{ {
m_warningLabel->clear();
QTreeWidgetItem *current = commandList()->currentItem(); QTreeWidgetItem *current = commandList()->currentItem();
if (!current || !current->data(0, Qt::UserRole).isValid()) if (!current || !current->data(0, Qt::UserRole).isValid())
return false; return true;
ShortcutItem *item = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole)); ShortcutItem *item = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
if (!item) QTC_ASSERT(item, return true);
return false;
const QKeySequence currentKey = QKeySequence::fromString(targetEdit()->text(), QKeySequence::NativeText); bool valid = false;
if (currentKey.isEmpty())
return false;
const Id globalId(Constants::C_GLOBAL); const QString text = m_shortcutEdit->text().trimmed();
const Context itemContext = item->m_cmd->context(); const QKeySequence currentKey = keySequenceFromEditString(text);
foreach (ShortcutItem *listItem, m_scitems) { if (keySequenceIsValid(currentKey) || text.isEmpty()) {
if (item == listItem) item->m_key = currentKey;
continue; auto that = const_cast<ShortcutSettingsWidget *>(this);
if (listItem->m_key.isEmpty()) if (item->m_cmd->defaultKeySequence() != item->m_key)
continue; that->setModified(current, true);
if (listItem->m_key.matches(currentKey) == QKeySequence::NoMatch) else
continue; that->setModified(current, false);
current->setText(2, item->m_key.toString(QKeySequence::NativeText));
const Context listContext = listItem->m_cmd->context(); valid = !that->markCollisions(item);
if (itemContext.contains(globalId) && !listContext.isEmpty()) if (!valid) {
return true; m_warningLabel->setText(
if (listContext.contains(globalId) && !itemContext.isEmpty()) tr("Key sequence has potential conflicts. <a href=\"#conflicts\">Show.</a>"));
return true;
foreach (Id id, listContext)
if (itemContext.contains(id))
return true;
} }
return false; } else {
m_warningLabel->setText(tr("Invalid key sequence."));
}
return valid;
} }
bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidgetItem *item, bool ShortcutSettingsWidget::filterColumn(const QString &filterString, QTreeWidgetItem *item,
int column) const int column) const
{ {
QString text = item->text(column); QString text;
if (Utils::HostOsInfo::isMacHost()) {
// accept e.g. Cmd+E in the filter. the text shows special fancy characters for Cmd
if (column == item->columnCount() - 1) { if (column == item->columnCount() - 1) {
QKeySequence key = QKeySequence::fromString(text, QKeySequence::NativeText); // filter on the shortcut edit text
if (!key.isEmpty()) { if (!item->data(0, Qt::UserRole).isValid())
text = key.toString(QKeySequence::PortableText); return true;
text.replace(QLatin1String("Ctrl"), QLatin1String("Cmd")); ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(item->data(0, Qt::UserRole));
text.replace(QLatin1String("Meta"), QLatin1String("Ctrl")); text = keySequenceToEditString(scitem->m_key);
text.replace(QLatin1String("Alt"), QLatin1String("Opt")); } else {
} text = item->text(column);
}
} }
return !text.contains(filterString, Qt::CaseInsensitive); return !text.contains(filterString, Qt::CaseInsensitive);
} }
void ShortcutSettingsWidget::setKeySequence(const QKeySequence &key) void ShortcutSettingsWidget::setKeySequence(const QKeySequence &key)
{ {
m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0; m_shortcutEdit->setText(keySequenceToEditString(key));
m_keyNum = key.count();
for (int i = 0; i < m_keyNum; ++i) {
m_key[i] = key[i];
}
targetEdit()->setText(key.toString(QKeySequence::NativeText));
} }
void ShortcutSettingsWidget::resetTargetIdentifier() void ShortcutSettingsWidget::showConflicts()
{
QTreeWidgetItem *current = commandList()->currentItem();
if (current && current->data(0, Qt::UserRole).isValid()) {
ShortcutItem *scitem = qvariant_cast<ShortcutItem *>(current->data(0, Qt::UserRole));
setFilterText(keySequenceToEditString(scitem->m_key));
}
}
void ShortcutSettingsWidget::resetToDefault()
{ {
QTreeWidgetItem *current = commandList()->currentItem(); QTreeWidgetItem *current = commandList()->currentItem();
if (current && current->data(0, Qt::UserRole).isValid()) { if (current && current->data(0, Qt::UserRole).isValid()) {
@@ -256,15 +409,6 @@ void ShortcutSettingsWidget::resetTargetIdentifier()
} }
} }
void ShortcutSettingsWidget::removeTargetIdentifier()
{
m_keyNum = m_key[0] = m_key[1] = m_key[2] = m_key[3] = 0;
targetEdit()->clear();
foreach (ShortcutItem *item, m_scitems)
markCollisions(item);
}
void ShortcutSettingsWidget::importAction() void ShortcutSettingsWidget::importAction()
{ {
QString fileName = QFileDialog::getOpenFileName(ICore::dialogParent(), tr("Import Keyboard Mapping Scheme"), QString fileName = QFileDialog::getOpenFileName(ICore::dialogParent(), tr("Import Keyboard Mapping Scheme"),
@@ -281,7 +425,7 @@ void ShortcutSettingsWidget::importAction()
item->m_key = mapping.value(sid); item->m_key = mapping.value(sid);
item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText)); item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText));
if (item->m_item == commandList()->currentItem()) if (item->m_item == commandList()->currentItem())
commandChanged(item->m_item); currentCommandChanged(item->m_item);
if (item->m_cmd->defaultKeySequence() != item->m_key) if (item->m_cmd->defaultKeySequence() != item->m_key)
setModified(item->m_item, true); setModified(item->m_item, true);
@@ -302,7 +446,7 @@ void ShortcutSettingsWidget::defaultAction()
item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText)); item->m_item->setText(2, item->m_key.toString(QKeySequence::NativeText));
setModified(item->m_item, false); setModified(item->m_item, false);
if (item->m_item == commandList()->currentItem()) if (item->m_item == commandList()->currentItem())
commandChanged(item->m_item); currentCommandChanged(item->m_item);
} }
foreach (ShortcutItem *item, m_scitems) foreach (ShortcutItem *item, m_scitems)
@@ -377,66 +521,39 @@ void ShortcutSettingsWidget::initialize()
filterChanged(filterText()); filterChanged(filterText());
} }
void ShortcutSettingsWidget::handleKeyEvent(QKeyEvent *e) bool ShortcutSettingsWidget::markCollisions(ShortcutItem *item)
{
int nextKey = e->key();
if ( m_keyNum > 3 ||
nextKey == Qt::Key_Control ||
nextKey == Qt::Key_Shift ||
nextKey == Qt::Key_Meta ||
nextKey == Qt::Key_Alt )
return;
nextKey |= translateModifiers(e->modifiers(), e->text());
switch (m_keyNum) {
case 0:
m_key[0] = nextKey;
break;
case 1:
m_key[1] = nextKey;
break;
case 2:
m_key[2] = nextKey;
break;
case 3:
m_key[3] = nextKey;
break;
default:
break;
}
m_keyNum++;
QKeySequence ks(m_key[0], m_key[1], m_key[2], m_key[3]);
targetEdit()->setText(ks.toString(QKeySequence::NativeText));
e->accept();
}
void ShortcutSettingsWidget::markCollisions(ShortcutItem *item)
{ {
bool hasCollision = false; bool hasCollision = false;
if (!item->m_key.isEmpty()) { if (!item->m_key.isEmpty()) {
Id globalId = Context(Constants::C_GLOBAL).at(0); 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);
foreach (ShortcutItem *currentItem, m_scitems) { foreach (ShortcutItem *currentItem, m_scitems) {
if (currentItem->m_key.isEmpty() || item == currentItem if (currentItem->m_key.isEmpty() || item == currentItem
|| item->m_key != currentItem->m_key) || item->m_key != currentItem->m_key)
continue; continue;
const Context currentContext = currentItem->m_cmd->context();
foreach (Id id, currentItem->m_cmd->context()) { bool currentIsConflicting = (itemHasGlobalContext && !currentContext.isEmpty());
// conflict if context is identical, OR if one if (!currentIsConflicting) {
// of the contexts is the global context foreach (const Id &id, currentContext) {
const Context thisContext = currentItem->m_cmd->context(); if ((id == globalId && !itemContext.isEmpty())
if (itemContext.contains(id) || itemContext.contains(id)) {
|| (itemContext.contains(globalId) && !thisContext.isEmpty()) currentIsConflicting = true;
|| (thisContext.contains(globalId) && !itemContext.isEmpty())) { break;
currentItem->m_item->setForeground(2, Qt::red); }
}
}
if (currentIsConflicting) {
currentItem->m_item->setForeground(
2, Utils::creatorTheme()->color(Utils::Theme::TextColorError));
hasCollision = true; hasCollision = true;
} }
} }
} }
} item->m_item->setForeground(2, hasCollision
item->m_item->setForeground(2, hasCollision ? Qt::red : commandList()->palette().foreground()); ? Utils::creatorTheme()->color(Utils::Theme::TextColorError)
: commandList()->palette().foreground());
return hasCollision;
} }
} // namespace Internal } // namespace Internal

View File

@@ -36,9 +36,12 @@
#include <QKeySequence> #include <QKeySequence>
#include <QPointer> #include <QPointer>
#include <QPushButton>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QGroupBox;
class QKeyEvent; class QKeyEvent;
class QLabel;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace Core { namespace Core {
@@ -57,6 +60,31 @@ struct ShortcutItem
QTreeWidgetItem *m_item; QTreeWidgetItem *m_item;
}; };
class ShortcutButton : public QPushButton
{
Q_OBJECT
public:
ShortcutButton(QWidget *parent = 0);
QSize sizeHint() const;
signals:
void keySequenceChanged(const QKeySequence &sequence);
protected:
bool eventFilter(QObject *obj, QEvent *evt);
private:
void updateText();
void handleToggleChange(bool toggleState);
QString m_checkedText;
QString m_uncheckedText;
mutable int m_preferredWidth = -1;
int m_key[4] = { 0, 0, 0, 0 };
int m_keyNum = 0;
};
class ShortcutSettingsWidget : public CommandMappings class ShortcutSettingsWidget : public CommandMappings
{ {
Q_OBJECT Q_OBJECT
@@ -68,28 +96,25 @@ public:
void apply(); void apply();
protected: protected:
bool eventFilter(QObject *o, QEvent *e) override;
void commandChanged(QTreeWidgetItem *current) override;
void targetIdentifierChanged() override;
void resetTargetIdentifier() override;
void removeTargetIdentifier() override;
void importAction() override; void importAction() override;
void exportAction() override; void exportAction() override;
void defaultAction() override; void defaultAction() override;
bool hasConflicts() const override;
bool filterColumn(const QString &filterString, QTreeWidgetItem *item, int column) const override; bool filterColumn(const QString &filterString, QTreeWidgetItem *item, int column) const override;
private: private:
void initialize(); void initialize();
void handleKeyEvent(QKeyEvent *e); void handleCurrentCommandChanged(QTreeWidgetItem *current);
void markCollisions(ShortcutItem *); void resetToDefault();
bool validateShortcutEdit() const;
bool markCollisions(ShortcutItem *);
void setKeySequence(const QKeySequence &key); void setKeySequence(const QKeySequence &key);
void showConflicts();
void clear(); void clear();
QList<ShortcutItem *> m_scitems; QList<ShortcutItem *> m_scitems;
int m_key[4], m_keyNum; QGroupBox *m_shortcutBox;
Utils::FancyLineEdit *m_shortcutEdit;
QLabel *m_warningLabel;
}; };
class ShortcutSettings : public IOptionsPage class ShortcutSettings : public IOptionsPage

View File

@@ -544,25 +544,14 @@ class FakeVimExCommandsWidget : public CommandMappings
Q_OBJECT Q_OBJECT
public: public:
FakeVimExCommandsWidget(FakeVimPluginPrivate *q, QWidget *parent = 0) FakeVimExCommandsWidget(FakeVimPluginPrivate *q, QWidget *parent = 0);
: CommandMappings(parent), m_q(q)
{
setPageTitle(Tr::tr("Ex Command Mapping"));
setTargetHeader(Tr::tr("Ex Trigger Expression"));
setTargetLabelText(Tr::tr("Regular expression:"));
setTargetEditTitle(Tr::tr("Ex Command"));
targetEdit()->setPlaceholderText(QString());
setImportExportEnabled(false);
initialize();
}
protected: protected:
void targetIdentifierChanged() override; void commandChanged();
void resetTargetIdentifier() override; void resetToDefault();
void removeTargetIdentifier() override;
void defaultAction() override; void defaultAction() override;
void commandChanged(QTreeWidgetItem *current) override; void handleCurrentCommandChanged(QTreeWidgetItem *current);
private: private:
void initialize(); void initialize();
@@ -571,8 +560,41 @@ private:
ExCommandMap &defaultExCommandMap(); ExCommandMap &defaultExCommandMap();
FakeVimPluginPrivate *m_q; FakeVimPluginPrivate *m_q;
QGroupBox *m_commandBox;
Utils::FancyLineEdit *m_commandEdit;
}; };
FakeVimExCommandsWidget::FakeVimExCommandsWidget(FakeVimPluginPrivate *q, QWidget *parent)
: CommandMappings(parent), m_q(q)
{
setPageTitle(Tr::tr("Ex Command Mapping"));
setTargetHeader(Tr::tr("Ex Trigger Expression"));
setImportExportEnabled(false);
connect(this, &FakeVimExCommandsWidget::currentCommandChanged,
this, &FakeVimExCommandsWidget::handleCurrentCommandChanged);
m_commandBox = new QGroupBox(Tr::tr("Ex Command"), this);
m_commandBox->setEnabled(false);
auto boxLayout = new QHBoxLayout(m_commandBox);
m_commandEdit = new Utils::FancyLineEdit(m_commandBox);
m_commandEdit->setFiltering(true);
m_commandEdit->setPlaceholderText(QString());
connect(m_commandEdit, &Utils::FancyLineEdit::textChanged,
this, &FakeVimExCommandsWidget::commandChanged);
auto resetButton = new QPushButton(Tr::tr("Reset"), m_commandBox);
resetButton->setToolTip(Tr::tr("Reset to default."));
connect(resetButton, &QPushButton::clicked,
this, &FakeVimExCommandsWidget::resetToDefault);
boxLayout->addWidget(new QLabel(Tr::tr("Regular expression:")));
boxLayout->addWidget(m_commandEdit);
boxLayout->addWidget(resetButton);
layout()->addWidget(m_commandBox);
initialize();
}
class FakeVimExCommandsPage : public IOptionsPage class FakeVimExCommandsPage : public IOptionsPage
{ {
Q_OBJECT Q_OBJECT
@@ -647,24 +669,28 @@ void FakeVimExCommandsWidget::initialize()
setModified(item, true); setModified(item, true);
} }
commandChanged(0); handleCurrentCommandChanged(0);
} }
void FakeVimExCommandsWidget::commandChanged(QTreeWidgetItem *current) void FakeVimExCommandsWidget::handleCurrentCommandChanged(QTreeWidgetItem *current)
{ {
CommandMappings::commandChanged(current); if (current) {
if (current) m_commandEdit->setText(current->text(2));
targetEdit()->setText(current->text(2)); m_commandBox->setEnabled(true);
} else {
m_commandEdit->clear();
m_commandBox->setEnabled(false);
}
} }
void FakeVimExCommandsWidget::targetIdentifierChanged() void FakeVimExCommandsWidget::commandChanged()
{ {
QTreeWidgetItem *current = commandList()->currentItem(); QTreeWidgetItem *current = commandList()->currentItem();
if (!current) if (!current)
return; return;
const QString name = current->data(0, CommandRole).toString(); const QString name = current->data(0, CommandRole).toString();
const QString regex = targetEdit()->text(); const QString regex = m_commandEdit->text();
if (current->data(0, Qt::UserRole).isValid()) { if (current->data(0, Qt::UserRole).isValid()) {
current->setText(2, regex); current->setText(2, regex);
@@ -674,7 +700,7 @@ void FakeVimExCommandsWidget::targetIdentifierChanged()
setModified(current, regex != defaultExCommandMap()[name].pattern()); setModified(current, regex != defaultExCommandMap()[name].pattern());
} }
void FakeVimExCommandsWidget::resetTargetIdentifier() void FakeVimExCommandsWidget::resetToDefault()
{ {
QTreeWidgetItem *current = commandList()->currentItem(); QTreeWidgetItem *current = commandList()->currentItem();
if (!current) if (!current)
@@ -683,12 +709,7 @@ void FakeVimExCommandsWidget::resetTargetIdentifier()
QString regex; QString regex;
if (defaultExCommandMap().contains(name)) if (defaultExCommandMap().contains(name))
regex = defaultExCommandMap()[name].pattern(); regex = defaultExCommandMap()[name].pattern();
targetEdit()->setText(regex); m_commandEdit->setText(regex);
}
void FakeVimExCommandsWidget::removeTargetIdentifier()
{
targetEdit()->clear();
} }
void FakeVimExCommandsWidget::defaultAction() void FakeVimExCommandsWidget::defaultAction()
@@ -706,7 +727,7 @@ void FakeVimExCommandsWidget::defaultAction()
setModified(item, false); setModified(item, false);
item->setText(2, regex); item->setText(2, regex);
if (item == commandList()->currentItem()) if (item == commandList()->currentItem())
commandChanged(item); currentCommandChanged(item);
} }
} }
} }