Merge pull request #28 from exactly-one-kas/hotkey-manager

Adds support for customizable hotkeys
This commit is contained in:
Murmele 2021-12-30 22:48:37 +01:00 committed by GitHub
commit e8bbac9609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1469 additions and 49 deletions

BIN
rsrc/hotkeys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

118
rsrc/hotkeys.svg Normal file
View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg980"
height="64"
width="64"
version="1.1">
<metadata
id="metadata986">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs984" />
<rect
id="rect970"
fill="darkgray"
ry="6"
rx="6"
height="60"
width="60"
y="3"
x="3" />
<rect
style="fill:#bf8040;fill-opacity:1;stroke:#604020;stroke-opacity:1"
id="rect972"
stroke="darkslategray"
stroke-width="1"
fill="slategray"
ry="6"
rx="6"
height="60"
width="60"
y="1"
x="1" />
<g
transform="translate(-1,0.5)"
id="g2324">
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect2123"
width="46"
height="33"
x="9"
y="14" />
<g
style="fill:#000000"
id="g2347">
<rect
y="38"
x="17"
height="5"
width="30"
id="rect2125"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.94868332;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<rect
y="28"
x="19.5"
height="5"
width="5"
id="rect2298"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect4184"
width="5"
height="5"
x="29.5"
y="28" />
<rect
y="28"
x="39.5"
height="5"
width="5"
id="rect4186"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect4190"
width="5"
height="5"
x="14.5"
y="18" />
<rect
y="18"
x="24.5"
height="5"
width="5"
id="rect4192"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect4194"
width="5"
height="5"
x="34.5"
y="18" />
<rect
y="18"
x="44.5"
height="5"
width="5"
id="rect4196"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
rsrc/hotkeys@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -30,6 +30,8 @@
<file>github.png</file>
<file>github_dark.png</file>
<file>gitlab.png</file>
<file>hotkeys.png</file>
<file>hotkeys@2x.png</file>
<file>lfs.png</file>
<file>lfs@2x.png</file>
<file>logo-type.png</file>

View File

@ -117,10 +117,15 @@ void Settings::endGroup()
}
QVariant Settings::value(const QString &key) const
{
return value(key, defaultValue(key));
}
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
{
QSettings settings;
settings.beginGroup(group());
QVariant result = settings.value(key, defaultValue(key));
QVariant result = settings.value(key, defaultValue);
settings.endGroup();
return result;
}

View File

@ -34,6 +34,7 @@ public:
void endGroup();
QVariant value(const QString &key) const;
QVariant value(const QString &key, const QVariant &defaultValue) const;
QVariant defaultValue(const QString &key) const;
void setValue(const QString &key, const QVariant &value, bool refresh = false);

View File

@ -13,6 +13,7 @@ add_library(dialogs
DiffPanel.cpp
ExternalToolsDialog.cpp
ExternalToolsModel.cpp
HotkeysPanel.cpp
IconLabel.cpp
MergeDialog.cpp
NewBranchDialog.cpp

View File

@ -0,0 +1,562 @@
//
// Copyright (c) 2021, Gittyup
//
// This software is licensed under the MIT License. The LICENSE.md file
// describes the conditions under which this software may be distributed.
//
// Author: Kas (https://github.com/exactly-one-kas)
//
#include "HotkeysPanel.h"
#include "ui/HotkeyManager.h"
#include <QAbstractItemModel>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QKeySequence>
#include <QKeySequenceEdit>
#include <QLabel>
#include <QMap>
#include <QModelIndex>
#include <QRegularExpression>
#include <QString>
#include <QVBoxLayout>
#include <QVector>
namespace
{
class HotkeyGroupData;
class HotkeyKeyData;
class HotkeyData : public QObject
{
public:
enum class ObjectType
{
Key,
Group
};
HotkeyData(QObject *parent, HotkeyGroupData *group, const QString &label)
: QObject(parent), mGroup(group), mLabel(label)
{
}
QString label() const
{
return mLabel;
}
QString fullLabel() const;
HotkeyGroupData *group() const
{
return mGroup;
}
virtual ObjectType type() const = 0;
virtual void findConflicts(
const QKeySequence &keys,
QStringList &conflicts,
Hotkey target
) const = 0;
private:
HotkeyGroupData *mGroup;
QString mLabel;
};
class HotkeyGroupData : public HotkeyData
{
public:
HotkeyGroupData(
QObject *parent,
const QString &label,
HotkeyGroupData *group = nullptr
) : HotkeyData(parent, group, label)
{
}
virtual ObjectType type() const override
{
return ObjectType::Group;
}
virtual void findConflicts(
const QKeySequence &keys,
QStringList &conflicts,
Hotkey target
) const override
{
for (auto child: mChildren) {
child->findConflicts(keys, conflicts, target);
}
}
QVector<HotkeyData*> childrenData() const
{
return mChildren;
}
void addChildData(HotkeyData *data)
{
mChildren.append(data);
}
private:
QVector<HotkeyData*> mChildren;
};
class HotkeyKeyData : public HotkeyData
{
public:
HotkeyKeyData(HotkeyGroupData *group, const QString &label, Hotkey hotkey, HotkeyManager *manager)
: HotkeyData(group, group, label), mHotkey(hotkey), mManager(manager)
{
mKeys = hotkey.currentKeys(manager);
}
virtual ObjectType type() const override
{
return ObjectType::Key;
}
virtual void findConflicts(
const QKeySequence &keys,
QStringList &conflicts,
Hotkey target
) const override
{
if (mHotkey != target && !mKeys.isEmpty()) {
auto leftMatch = mKeys.matches(keys);
auto rightMatch = keys.matches(mKeys);
if (
leftMatch == QKeySequence::ExactMatch
// Recognize conflicts of partial matches on key sequences
// We have to do it this way since NoMatch also gets returned
// if one sequence is shorter than the other
|| (leftMatch == QKeySequence::PartialMatch && rightMatch == QKeySequence::NoMatch)
|| (leftMatch == QKeySequence::NoMatch && rightMatch == QKeySequence::PartialMatch)
) {
conflicts.push_back(fullLabel());
}
}
}
QKeySequence keys() const
{
return mKeys;
}
void setKeys(QKeySequence keys)
{
mHotkey.setKeys(keys, mManager);
mKeys = keys;
}
Hotkey hotkey() const
{
return mHotkey;
}
private:
Hotkey mHotkey;
HotkeyManager *mManager;
QKeySequence mKeys;
};
QString HotkeyData::fullLabel() const
{
QString res = label();
if (
mGroup
&& mGroup->mGroup // Don't use the root's label
) {
res = mGroup->fullLabel() + " -> " + res;
}
return res;
}
class HotkeyModel : public QAbstractItemModel
{
public:
enum class ColumnIndex : int
{
Min = 0,
Label = 0,
Keys = 1,
Max = 1
};
HotkeyModel(QObject *parent, HotkeyGroupData *root): QAbstractItemModel(parent), mRoot(root)
{
}
virtual int columnCount(const QModelIndex &parent) const override
{
return 2;
}
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (!parent.isValid())
return mRoot->childrenData().length();
HotkeyData *data = (HotkeyData*)parent.internalPointer();
if(data->type() != HotkeyData::ObjectType::Group)
return 0;
return ((HotkeyGroupData*)data)->childrenData().length();
}
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (row < 0 || column < (int)ColumnIndex::Min || column > (int)ColumnIndex::Max)
return QModelIndex();
HotkeyGroupData *group = nullptr;
if (parent.isValid()) {
HotkeyData *data = (HotkeyData*)parent.internalPointer();
if(data->type() == HotkeyData::ObjectType::Group)
group = (HotkeyGroupData*)data;
} else {
group = mRoot;
}
if(!group || row >= group->childrenData().length())
return QModelIndex();
return createIndex(row, column, group->childrenData()[row]);
}
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (
!index.isValid()
|| index.column() < (int)ColumnIndex::Min
|| index.column() > (int)ColumnIndex::Max
)
return QVariant();
HotkeyData *data = (HotkeyData*)index.internalPointer();
switch ((ColumnIndex)index.column()) {
case ColumnIndex::Label:
if (role == Qt::DisplayRole)
return QVariant(data->label());
else
return QVariant();
case ColumnIndex::Keys:
if(data->type() != HotkeyData::ObjectType::Key)
return QVariant();
else if(role == Qt::DisplayRole)
return QVariant(((HotkeyKeyData*)data)->keys().toString(QKeySequence::NativeText));
else if(role == Qt::UserRole)
return QVariant::fromValue(((HotkeyKeyData*)data)->keys());
else if(role == Qt::UserRole + 1)
return QVariant::fromValue(((HotkeyKeyData*)data)->hotkey());
else
return QVariant();
}
return QVariant();
}
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if (!index.isValid() || index.column() != (int)ColumnIndex::Keys || role != Qt::UserRole)
return false;
HotkeyData *data = (HotkeyData*)index.internalPointer();
if (data->type() != HotkeyData::ObjectType::Key)
return false;
((HotkeyKeyData*)data)->setKeys(value.value<QKeySequence>());
return true;
}
virtual QModelIndex parent(const QModelIndex &index) const override
{
if (
!index.isValid()
|| index.column() < (int)ColumnIndex::Min
|| index.column() > (int)ColumnIndex::Max
)
return QModelIndex();
HotkeyData *data = (HotkeyData*)index.internalPointer();
HotkeyGroupData *parent = data->group();
int parentRow = 0;
if (parent && parent->group())
parentRow = parent->group()->childrenData().indexOf(parent);
if(!parent)
return QModelIndex();
else
return createIndex(parentRow, 0, parent);
}
virtual Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (
!index.isValid()
|| index.column() < (int)ColumnIndex::Min
|| index.column() > (int)ColumnIndex::Max
)
return Qt::ItemFlag::NoItemFlags;
Qt::ItemFlags res = Qt::ItemFlag::ItemIsEnabled;
if (((HotkeyData*)index.internalPointer())->type() == HotkeyData::ObjectType::Key)
res |= Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemNeverHasChildren;
return res;
}
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
{
if (
orientation != Qt::Orientation::Horizontal
|| section < (int)ColumnIndex::Min
|| section > (int)ColumnIndex::Max
|| role != Qt::DisplayRole
)
return QVariant();
switch ((ColumnIndex)section) {
case ColumnIndex::Label:
return QVariant(tr("Action"));
case ColumnIndex::Keys:
return QVariant(tr("Keys"));
}
return QVariant();
}
void findConflicts(
const QKeySequence &keys,
QStringList &conflicts,
Hotkey target
) const
{
mRoot->findConflicts(keys, conflicts, target);
}
private:
HotkeyGroupData *mRoot;
};
class SimpleKeyEdit : public QKeySequenceEdit
{
public:
SimpleKeyEdit(QWidget *parent): QKeySequenceEdit(parent)
{
}
protected:
virtual void keyPressEvent(QKeyEvent *e) override
{
if (e->modifiers() == Qt::KeyboardModifier::NoModifier || e->modifiers() == Qt::KeyboardModifier::KeypadModifier)
{
switch (e->key()) {
case Qt::Key_Backspace:
case Qt::Key_Delete:
setKeySequence(QKeySequence());
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
e->ignore();
return;
}
}
QKeySequenceEdit::keyPressEvent(e);
}
};
class KeybindDialog : public QDialog
{
public:
KeybindDialog(
QWidget *parent,
HotkeyModel *hotkeys,
Hotkey hotkey
): QDialog(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
auto conflicts = new QLabel(this);
mKeys = new SimpleKeyEdit(this);
connect(
mKeys,
&SimpleKeyEdit::keySequenceChanged,
[conflicts, hotkey, hotkeys](const QKeySequence &keys) {
if (keys.isEmpty()) {
conflicts->setText("");
return;
}
auto conflictLabels = QStringList();
hotkeys->findConflicts(keys, conflictLabels, hotkey);
if (conflictLabels.isEmpty()) {
conflicts->setText("");
} else {
conflicts->setText(
tr("The selected key is the same for the following actions:\n%1")
.arg(" - " + conflictLabels.join("\n - "))
);
}
}
);
layout->addWidget(new QLabel(tr("Please press the desired hotkey"), this));
layout->addWidget(mKeys);
layout->addWidget(conflicts);
QDialogButtonBox *buttons = new QDialogButtonBox(this);
buttons->addButton(QDialogButtonBox::Ok);
buttons->addButton(QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttons);
mKeys->setFocus();
}
QKeySequence keys() const
{
return mKeys->keySequence();
}
void setKeys(const QKeySequence &keys)
{
mKeys->setKeySequence(keys);
}
private:
SimpleKeyEdit *mKeys;
};
}
HotkeysPanel::HotkeysPanel(QWidget *parent) : QTreeView(parent)
{
static QRegularExpression slashRegex("/+");
HotkeyManager *manager = HotkeyManager::instance();
QMap<QString, HotkeyGroupData*> groups;
HotkeyGroupData *root = new HotkeyGroupData(this, "<Root>");
// Build hotkey hierarchy
for (Hotkey hotkey: manager->knownHotkeys()) {
QString label = hotkey.label().replace(slashRegex, "/");
int lastSep = label.lastIndexOf('/');
HotkeyGroupData *group;
// Look for existing group along hierarchy
int pos = lastSep;
while (pos >= 0) {
group = groups.value(label.left(pos));
if (group)
break;
pos = label.lastIndexOf('/', pos - 1);
}
// Use root if no matching group has been found
if (!group) {
Q_ASSERT(pos < 0);
group = root;
pos = 0;
}
// Build and insert missing groups
while (pos < lastSep && pos >= 0) {
pos = label.indexOf('/', pos + 1);
QString path = label.left(pos);
int subPos = path.lastIndexOf('/');
group = new HotkeyGroupData(group, tr(path.mid(subPos + 1).toUtf8()), group);
group->group()->addChildData(group);
Q_ASSERT(!groups.contains(path));
groups.insert(path, group);
}
// Add hotkey to group
group->addChildData(new HotkeyKeyData(group, tr(label.mid(lastSep + 1).toUtf8()), hotkey, manager));
}
setModel(new HotkeyModel(this, root));
setUniformRowHeights(true);
setAllColumnsShowFocus(true);
setEditTriggers(EditTrigger::DoubleClicked | EditTrigger::EditKeyPressed);
expandAll();
header()->setSectionsMovable(false);
header()->setSectionResizeMode(0, QHeaderView::ResizeMode::ResizeToContents);
header()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch);
}
bool HotkeysPanel::edit(const QModelIndex &index, QAbstractItemView::EditTrigger trigger, QEvent *event)
{
if (!index.isValid() || !(trigger & editTriggers()))
return false;
QModelIndex keyIndex = index.siblingAtColumn((int)HotkeyModel::ColumnIndex::Keys);
QVariant data = keyIndex.data(Qt::UserRole);
if(!data.isValid())
return false;
KeybindDialog *dialog = new KeybindDialog(
this,
(HotkeyModel*) model(),
keyIndex.data(Qt::UserRole + 1).value<Hotkey>()
);
dialog->setKeys(data.value<QKeySequence>());
QPersistentModelIndex idx(keyIndex);
connect(dialog, &QDialog::accepted, [this, idx, dialog]() {
model()->setData(idx, QVariant::fromValue(dialog->keys()), Qt::UserRole);
});
dialog->setModal(true);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
return true;
}
QSize HotkeysPanel::sizeHint() const
{
return QSize(600, 320);
}
void HotkeysPanel::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
QModelIndexList indexes = selectedIndexes();
if (!indexes.isEmpty())
edit(indexes.first(), EditTrigger::EditKeyPressed, e);
return;
}
QTreeView::keyPressEvent(e);
}

View File

@ -0,0 +1,30 @@
//
// Copyright (c) 2021, Gittyup
//
// This software is licensed under the MIT License. The LICENSE.md file
// describes the conditions under which this software may be distributed.
//
// Author: Kas (https://github.com/exactly-one-kas)
//
#include <QKeyEvent>
#include <QTreeView>
class HotkeysPanel : public QTreeView
{
public:
enum Column
{
Name,
Kind,
Description
};
HotkeysPanel(QWidget *parent = nullptr);
virtual QSize sizeHint() const override;
protected:
virtual bool edit(const QModelIndex &index, QAbstractItemView::EditTrigger trigger, QEvent *event) override;
virtual void keyPressEvent(QKeyEvent *e) override;
};

View File

@ -11,6 +11,7 @@
#include "AboutDialog.h"
#include "DiffPanel.h"
#include "ExternalToolsDialog.h"
#include "HotkeysPanel.h"
#include "PluginsPanel.h"
#include "app/Application.h"
#include "app/CustomTheme.h"
@ -880,6 +881,14 @@ SettingsDialog::SettingsDialog(Index index, QWidget *parent)
stack->addWidget(new MiscPanel(this));
// Add hotkeys panel.
QAction *hotkeys = toolbar->addAction(QIcon(":/hotkeys.png"), tr("Hotkeys"));
hotkeys->setData(Hotkeys);
hotkeys->setActionGroup(actions);
hotkeys->setCheckable(true);
stack->addWidget(new HotkeysPanel(this));
#ifdef Q_OS_UNIX
// Add terminal panel.
QAction *terminal = toolbar->addAction(QIcon(":/terminal.png"), tr("Terminal"));

View File

@ -27,6 +27,7 @@ public:
Update,
Plugins,
Misc,
Hotkeys,
Terminal
};

View File

@ -27,6 +27,7 @@ add_library(ui
Footer.cpp
History.cpp
IgnoreDialog.cpp
HotkeyManager.cpp
IndexCompleter.cpp
Location.cpp
MainWindow.cpp

208
src/ui/HotkeyManager.cpp Normal file
View File

@ -0,0 +1,208 @@
//
// Copyright (c) 2021, Gittyup
//
// This software is licensed under the MIT License. The LICENSE.md file
// describes the conditions under which this software may be distributed.
//
// Author: Kas (https://github.com/exactly-one-kas)
//
#include "HotkeyManager.h"
struct HotkeyManagerHandle
{
const char *defaultKeys;
QKeySequence::StandardKey standardKey;
const char *configPath;
const char *label;
int index;
HotkeyManagerHandle *next;
};
static HotkeyManagerHandle *hotkeyRegistry;
#ifndef QT_NO_DEBUG
static bool consumed;
#endif
HotkeyHandle::HotkeyHandle(QMetaObject::Connection &&changeSignalConnection): changeSignalConnection(std::move(changeSignalConnection))
{
}
HotkeyHandle::~HotkeyHandle()
{
disconnect(changeSignalConnection);
}
HotkeyHandle *Hotkey::use(std::function<void(const QKeySequence&)> changeHandler, HotkeyManager *manager) const
{
if (!manager)
manager = HotkeyManager::instance();
QMetaObject::Connection connection = QObject::connect(manager, &HotkeyManager::changed, [this, changeHandler, manager](const HotkeyManagerHandle *handle) {
if (handle == mHandle)
changeHandler(manager->keys(mHandle));
});
changeHandler(manager->keys(mHandle));
return new HotkeyHandle(std::move(connection));
}
HotkeyHandle *Hotkey::use(QAction *action, HotkeyManager *manager) const
{
HotkeyHandle *res = use(
[action](const QKeySequence &keys) {
action->setShortcut(keys);
},
manager
);
res->setParent(action);
return res;
}
HotkeyHandle *Hotkey::use(QShortcut *shortcut, HotkeyManager *manager) const
{
HotkeyHandle *res = use(
[shortcut](const QKeySequence &keys) {
shortcut->setKey(keys);
},
manager
);
res->setParent(shortcut);
return res;
}
QString Hotkey::label() const
{
return mHandle->label;
}
QKeySequence Hotkey::currentKeys(const HotkeyManager *manager) const
{
if (!manager)
manager = HotkeyManager::instance();
return manager->keys(mHandle);
}
QString Hotkey::defaultKeys() const
{
return QObject::tr(mHandle->defaultKeys);
}
void Hotkey::setKeys(const QKeySequence &keys, HotkeyManager *manager)
{
if (!manager)
manager = HotkeyManager::instance();
manager->setKeys(mHandle, keys);
}
Hotkey::Hotkey(const HotkeyManagerHandle *handle): mHandle(handle)
{
}
HotkeyManager *HotkeyManager::instance()
{
static HotkeyManager *instance = nullptr;
if (!instance)
instance = new HotkeyManager();
return instance;
}
Hotkey HotkeyManager::registerHotkey(const char *defaultKeys, QKeySequence::StandardKey standardKey, const char *configPath, const char *label)
{
#ifndef QT_NO_DEBUG
Q_ASSERT_X(!consumed, "HotkeyManager::registerHotkey()", "Registering a hotkey after creating a hotkey manager");
#endif
HotkeyManagerHandle *info = new HotkeyManagerHandle();
info->defaultKeys = defaultKeys;
info->standardKey = standardKey;
info->configPath = configPath;
info->label = label;
info->next = hotkeyRegistry;
if (hotkeyRegistry)
info->index = hotkeyRegistry->index + 1;
else
info->index = 0;
hotkeyRegistry = info;
return Hotkey(info);
}
Hotkey HotkeyManager::registerHotkey(const char *defaultKeys, const char *configPath, const char *label)
{
if (!defaultKeys)
defaultKeys = "";
return registerHotkey(defaultKeys, QKeySequence::StandardKey::UnknownKey, configPath, label);
}
Hotkey HotkeyManager::registerHotkey(QKeySequence::StandardKey standardKey, const char *configPath, const char *label)
{
return registerHotkey(nullptr, standardKey, configPath, label);
}
static QKeySequence getKeys(const QString &setting, HotkeyManagerHandle *handle)
{
if (!setting.isEmpty())
return QKeySequence(setting, QKeySequence::SequenceFormat::PortableText);
else if (!handle->defaultKeys)
return QKeySequence(handle->standardKey);
else
return QKeySequence(handle->defaultKeys);
}
HotkeyManager::HotkeyManager(Settings *settings):
mSettings(settings),
mHandles(hotkeyRegistry->index + 1),
mKeys(hotkeyRegistry->index + 1)
{
#ifndef QT_NO_DEBUG
consumed = true;
#endif
if (!mSettings)
mSettings = Settings::instance();
mSettings->beginGroup("hotkeys");
for (HotkeyManagerHandle *i = hotkeyRegistry; i; i = i->next) {
mHandles[i->index] = i;
mKeys[i->index] = getKeys(mSettings->value(i->configPath, "").toString(), i);
}
mSettings->endGroup();
}
QVector<Hotkey> HotkeyManager::knownHotkeys() const
{
QVector<Hotkey> res(mHandles.size());
for (HotkeyManagerHandle *i: mHandles)
res[i->index] = Hotkey(i);
return res;
}
QKeySequence HotkeyManager::keys(const HotkeyManagerHandle *handle) const
{
return mKeys[handle->index];
}
void HotkeyManager::setKeys(const HotkeyManagerHandle *handle, const QKeySequence &keys)
{
mKeys[handle->index] = keys;
mSettings->setValue("hotkeys/" + QString(handle->configPath), keys.toString());
emit changed(handle);
}

104
src/ui/HotkeyManager.h Normal file
View File

@ -0,0 +1,104 @@
//
// Copyright (c) 2021, Gittyup
//
// This software is licensed under the MIT License. The LICENSE.md file
// describes the conditions under which this software may be distributed.
//
// Author: Kas (https://github.com/exactly-one-kas)
//
#ifndef HOTKEYMANAGER_H
#define HOTKEYMANAGER_H
#include "conf/Settings.h"
#include <functional>
#include <QAction>
#include <QObject>
#include <QKeySequence>
#include <QShortcut>
#include <QString>
#include <QVector>
class HotkeyManager;
struct HotkeyManagerHandle;
class HotkeyHandle : public QObject
{
public:
HotkeyHandle(QMetaObject::Connection &&changeSignalConnection);
~HotkeyHandle();
private:
QMetaObject::Connection changeSignalConnection;
};
class Hotkey
{
friend class HotkeyManager;
public:
inline Hotkey(): mHandle(nullptr)
{
}
HotkeyHandle *use(std::function<void(const QKeySequence&)> changeHandler, HotkeyManager *manager = nullptr) const;
HotkeyHandle *use(QAction *action, HotkeyManager *manager = nullptr) const;
HotkeyHandle *use(QShortcut *shortcut, HotkeyManager *manager = nullptr) const;
QString label() const;
QKeySequence currentKeys(const HotkeyManager *manager = nullptr) const;
QString defaultKeys() const;
void setKeys(const QKeySequence &keys, HotkeyManager *manager = nullptr);
inline bool isValid() const
{
return mHandle != nullptr;
}
inline bool operator ==(const Hotkey &op) const
{
return mHandle == op.mHandle;
}
inline bool operator !=(const Hotkey &op) const
{
return mHandle != op.mHandle;
}
private:
const HotkeyManagerHandle *mHandle;
Hotkey(const HotkeyManagerHandle *handle);
};
Q_DECLARE_METATYPE(Hotkey)
class HotkeyManager : public QObject
{
friend class Hotkey;
Q_OBJECT
public:
static Hotkey registerHotkey(const char *defaultKeys, const char *configPath, const char *label);
static Hotkey registerHotkey(QKeySequence::StandardKey defaultKeys, const char *configPath, const char *label);
static HotkeyManager *instance();
HotkeyManager(Settings *settings = nullptr);
QVector<Hotkey> knownHotkeys() const;
signals:
void changed(const HotkeyManagerHandle *handle);
private:
static Hotkey registerHotkey(const char *defaultKeys, QKeySequence::StandardKey standardKey, const char *configPath, const char *label);
Settings *mSettings;
QVector<HotkeyManagerHandle*> mHandles;
QVector<QKeySequence> mKeys;
QKeySequence keys(const HotkeyManagerHandle *handle) const;
void setKeys(const HotkeyManagerHandle *handle, const QKeySequence &keys);
};
#endif

View File

@ -13,6 +13,7 @@
#include "EditorWindow.h"
#include "FindWidget.h"
#include "History.h"
#include "HotkeyManager.h"
#include "MainWindow.h"
#include "RepoView.h"
#include "TabWidget.h"
@ -66,6 +67,350 @@ void openCloneDialog(CloneDialog::Kind kind)
bool MenuBar::sDebugMenuVisible = false;
static Hotkey newFileHotkey = HotkeyManager::registerHotkey(
QKeySequence::New,
"file/new",
"File/New File"
);
static Hotkey newWinHotkey = HotkeyManager::registerHotkey(
"Ctrl+Meta+N",
"file/newWindow",
"File/New Window"
);
static Hotkey cloneHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+N",
"file/clone",
"File/Clone Repository"
);
static Hotkey initHotkey = HotkeyManager::registerHotkey(
"Ctrl+Alt+N",
"file/init",
"File/Initialize New Repository"
);
static Hotkey openHotkey = HotkeyManager::registerHotkey(
QKeySequence::Open,
"file/open",
"File/Open Repository"
);
static Hotkey closeHotkey = HotkeyManager::registerHotkey(
QKeySequence::Close,
"file/close",
"File/Close"
);
static Hotkey saveHotkey = HotkeyManager::registerHotkey(
QKeySequence::Save,
"file/save",
"File/Save"
);
#ifndef Q_OS_MAC
static Hotkey quitHotkey = HotkeyManager::registerHotkey(
QKeySequence::Quit,
"file/quit",
"File/Exit"
);
#endif
static Hotkey undoHotkey = HotkeyManager::registerHotkey(
QKeySequence::Undo,
"edit/undo",
"Edit/Undo"
);
static Hotkey redoHotkey = HotkeyManager::registerHotkey(
QKeySequence::Redo,
"edit/redo",
"Edit/Redo"
);
static Hotkey cutHotkey = HotkeyManager::registerHotkey(
QKeySequence::Cut,
"edit/cut",
"Edit/Cut"
);
static Hotkey copyHotkey = HotkeyManager::registerHotkey(
QKeySequence::Copy,
"edit/copy",
"Edit/Copy"
);
static Hotkey pasteHotkey = HotkeyManager::registerHotkey(
QKeySequence::Paste,
"edit/paste",
"Edit/Paste"
);
static Hotkey selectAllHotkey = HotkeyManager::registerHotkey(
QKeySequence::SelectAll,
"edit/selectAll",
"Edit/Select All"
);
static Hotkey findHotkey = HotkeyManager::registerHotkey(
QKeySequence::Find,
"edit/find",
"Edit/Find"
);
static Hotkey findNextHotkey = HotkeyManager::registerHotkey(
QKeySequence::FindNext,
"edit/findNext",
"Edit/Find Next"
);
static Hotkey findPreviousHotkey = HotkeyManager::registerHotkey(
QKeySequence::FindPrevious,
"edit/findPrevious",
"Edit/Find Previous"
);
static Hotkey findSelectionHotkey = HotkeyManager::registerHotkey(
"Ctrl+E",
"edit/findSelection",
"Edit/Use Selection for Find"
);
static Hotkey refreshHotkey = HotkeyManager::registerHotkey(
QKeySequence::Refresh,
"view/refresh",
"View/Refresh"
);
static Hotkey toggleLogHotkey = HotkeyManager::registerHotkey(
nullptr,
"view/toggleLog",
"View/Toggle Log"
);
static Hotkey toggleMaximizeHotkey = HotkeyManager::registerHotkey(
"Ctrl+M",
"view/toggleMaximize",
"View/Toggle Maximize"
);
static Hotkey toggleViewHotkey = HotkeyManager::registerHotkey(
nullptr,
"view/toggleView",
"View/Toggle Tree View"
);
static Hotkey configureRepositoryHotkey = HotkeyManager::registerHotkey(
nullptr,
"repository/configure",
"Repository/Configure Repository"
);
static Hotkey stageAllHotkey = HotkeyManager::registerHotkey(
"Ctrl++",
"repository/stageAll",
"Repository/Stage All"
);
static Hotkey unstageAllHotkey = HotkeyManager::registerHotkey(
"Ctrl+-",
"repository/unstageAll",
"Repository/Unstage All"
);
static Hotkey commitHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+C",
"repository/commit",
"Repository/Commit"
);
static Hotkey amendCommitHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+A",
"repository/amendCommit",
"Repository/Amend Commit"
);
static Hotkey lfsUnlockHotkey = HotkeyManager::registerHotkey(
nullptr,
"repository/lfs/unlock",
"Repository/Remove all LFS locks"
);
static Hotkey lfsInitializeHotkey = HotkeyManager::registerHotkey(
nullptr,
"repository/lfs/initialize",
"Repository/Initialize LFS"
);
static Hotkey configureRemotesHotkey = HotkeyManager::registerHotkey(
nullptr,
"remote/configure",
"Remote/Configure"
);
static Hotkey fetchHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+F",
"remote/fetch",
"Remote/Fetch"
);
static Hotkey fetchAllHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+A",
"remote/fetchAll",
"Remote/Fetch All"
);
static Hotkey fetchFromHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+F",
"remote/fetchFrom",
"Remote/Fetch From"
);
static Hotkey pullHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+L",
"remote/pull",
"Remote/Pull"
);
static Hotkey pullFromHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+L",
"remote/pullFrom",
"Remote/Pull From"
);
static Hotkey pushHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+P",
"remote/push",
"Remote/Push"
);
static Hotkey pushToHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+P",
"remote/pushTo",
"Remote/Push To"
);
static Hotkey configureBranchesHotkey = HotkeyManager::registerHotkey(
nullptr,
"branch/configure",
"Branch/Configure"
);
static Hotkey newBranchHotkey = HotkeyManager::registerHotkey(
nullptr,
"branch/new",
"Branch/New"
);
static Hotkey checkoutCurrentHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+H",
"branch/checkoutCurrent",
"Branch/Checkout Current"
);
static Hotkey checkoutHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+H",
"branch/checkout",
"Branch/Checkout"
);
static Hotkey mergeHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+M",
"branch/merge",
"Branch/Merge"
);
static Hotkey rebaseHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+R",
"branch/rebase",
"Branch/Rebase"
);
static Hotkey abortHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+A",
"branch/abort",
"Branch/Abort Merge"
);
static Hotkey configureSubmodulesHotkey = HotkeyManager::registerHotkey(
nullptr,
"branch/configure",
"Branch/Configure"
);
static Hotkey updateSubmodulesHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+U",
"branch/update",
"Branch/Update All"
);
static Hotkey initSubmodulesHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+U",
"branch/init",
"Branch/Update"
);
static Hotkey showStashesHotkey = HotkeyManager::registerHotkey(
nullptr,
"stash/show",
"Stash/Show Stashes"
);
static Hotkey stashHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+T",
"stash/stash",
"Stash/Stash"
);
static Hotkey stashPopHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Alt+T",
"stash/pop",
"Stash/Pop"
);
static Hotkey prevHotkey = HotkeyManager::registerHotkey(
QKeySequence::Back,
"history/prev",
"History/Back"
);
static Hotkey nextHotkey = HotkeyManager::registerHotkey(
QKeySequence::Forward,
"history/next",
"History/Forward"
);
static Hotkey prevTabHotkey = HotkeyManager::registerHotkey(
QKeySequence::PreviousChild,
"window/prevTab",
"Window/Show Previous Tab"
);
static Hotkey nextTabHotkey = HotkeyManager::registerHotkey(
QKeySequence::NextChild,
"window/nextTab",
"Window/Show Next Tab"
);
static Hotkey chooserHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+O",
"window/chooser",
"Window/Show Repository Chooser"
);
static Hotkey preferencesHotkey = HotkeyManager::registerHotkey(
nullptr,
"tools/preferences",
"Tools/Options"
);
static Hotkey squashHotkey = HotkeyManager::registerHotkey(
"Ctrl+Shift+Q",
"tools/preferences",
"Tools/Options"
);
MenuBar::MenuBar(QWidget *parent)
: QMenuBar(parent)
{
@ -73,7 +418,7 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *file = addMenu(tr("File"));
QAction *newFile = file->addAction(tr("New File"));
newFile->setShortcut(QKeySequence::New);
newFileHotkey.use(newFile);
connect(newFile, &QAction::triggered, [] {
if (MainWindow *window = MainWindow::activeWindow()) {
if (RepoView *view = window->currentView()) {
@ -87,19 +432,19 @@ MenuBar::MenuBar(QWidget *parent)
});
QAction *newWin = file->addAction(tr("New Window"));
newWin->setShortcut(tr("Ctrl+Meta+N"));
newWinHotkey.use(newWin);
connect(newWin, &QAction::triggered, [] {
MainWindow::open();
});
QAction *clone = file->addAction(tr("Clone Repository..."));
clone->setShortcut(tr("Ctrl+Shift+N"));
cloneHotkey.use(clone);
connect(clone, &QAction::triggered, [] {
openCloneDialog(CloneDialog::Clone);
});
QAction *init = file->addAction(tr("Initialize New Repository..."));
init->setShortcut(tr("Ctrl+Alt+N"));
initHotkey.use(init);
connect(init, &QAction::triggered, [] {
openCloneDialog(CloneDialog::Init);
});
@ -107,7 +452,7 @@ MenuBar::MenuBar(QWidget *parent)
file->addSeparator();
QAction *open = file->addAction(tr("Open Repository..."));
open->setShortcut(QKeySequence::Open);
openHotkey.use(open);
connect(open, &QAction::triggered, [] {
// FIXME: Filter out non-git dirs.
Settings *settings = Settings::instance();
@ -134,7 +479,7 @@ MenuBar::MenuBar(QWidget *parent)
file->addSeparator();
mClose = file->addAction(tr("Close"));
mClose->setShortcut(QKeySequence::Close);
closeHotkey.use(mClose);
connect(mClose, &QAction::triggered, [] {
QWidget *window = QApplication::activeWindow();
if (!window)
@ -151,7 +496,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mSave = file->addAction(tr("Save"));
mSave->setShortcut(QKeySequence::Save);
saveHotkey.use(mSave);
mSave->setEnabled(false);
connect(mSave, &QAction::triggered, [this] {
static_cast<EditorWindow *>(window())->widget()->save();
@ -162,7 +507,7 @@ MenuBar::MenuBar(QWidget *parent)
#ifndef Q_OS_MAC
QAction *quit = file->addAction(tr("Exit"));
quit->setMenuRole(QAction::QuitRole);
quit->setShortcut(QKeySequence::Quit);
quitHotkey.use(quit);
connect(quit, &QAction::triggered, &QApplication::closeAllWindows);
#endif
@ -170,7 +515,7 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *edit = addMenu(tr("Edit"));
mUndo = edit->addAction(tr("Undo"));
mUndo->setShortcut(QKeySequence::Undo);
undoHotkey.use(mUndo);
connect(mUndo, &QAction::triggered, [] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -183,7 +528,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mRedo = edit->addAction(tr("Redo"));
mRedo->setShortcut(QKeySequence::Redo);
redoHotkey.use(mRedo);
connect(mRedo, &QAction::triggered, [this] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -198,7 +543,7 @@ MenuBar::MenuBar(QWidget *parent)
edit->addSeparator();
mCut = edit->addAction(tr("Cut"));
mCut->setShortcut(QKeySequence::Cut);
cutHotkey.use(mCut);
connect(mCut, &QAction::triggered, [] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -211,7 +556,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mCopy = edit->addAction(tr("Copy"));
mCopy->setShortcut(QKeySequence::Copy);
copyHotkey.use(mCopy);
connect(mCopy, &QAction::triggered, [] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -226,7 +571,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mPaste = edit->addAction(tr("Paste"));
mPaste->setShortcut(QKeySequence::Paste);
pasteHotkey.use(mPaste);
connect(mPaste, &QAction::triggered, [] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -239,7 +584,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mSelectAll = edit->addAction(tr("Select All"));
mSelectAll->setShortcut(QKeySequence::SelectAll);
selectAllHotkey.use(mSelectAll);
connect(mSelectAll, &QAction::triggered, [] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -255,7 +600,7 @@ MenuBar::MenuBar(QWidget *parent)
mFind = edit->addAction(tr("Find..."));
mFind->setObjectName("Find");
mFind->setShortcut(QKeySequence::Find);
findHotkey.use(mFind);
connect(mFind, &QAction::triggered, [] {
QWidget *widget = QApplication::activeWindow();
if (MainWindow *window = qobject_cast<MainWindow *>(widget)) {
@ -266,7 +611,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mFindNext = edit->addAction(tr("Find Next"));
mFindNext->setShortcut(QKeySequence::FindNext);
findNextHotkey.use(mFindNext);
connect(mFindNext, &QAction::triggered, [] {
QWidget *widget = QApplication::activeWindow();
if (MainWindow *window = qobject_cast<MainWindow *>(widget)) {
@ -277,7 +622,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mFindPrevious = edit->addAction(tr("Find Previous"));
mFindPrevious->setShortcut(QKeySequence::FindPrevious);
findPreviousHotkey.use(mFindPrevious);
connect(mFindPrevious, &QAction::triggered, [] {
QWidget *widget = QApplication::activeWindow();
if (MainWindow *window = qobject_cast<MainWindow *>(widget)) {
@ -288,7 +633,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mFindSelection = edit->addAction(tr("Use Selection for Find"));
mFindSelection->setShortcut(tr("Ctrl+E"));
findSelectionHotkey.use(mFindSelection);
connect(mFindSelection, &QAction::triggered, [this] {
QWidget *widget = QApplication::focusWidget();
if (TextEditor *editor = qobject_cast<TextEditor *>(widget)) {
@ -307,7 +652,7 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *viewMenu = addMenu(tr("View"));
mRefresh = viewMenu->addAction(tr("Refresh"));
mRefresh->setShortcut(QKeySequence::Refresh);
refreshHotkey.use(mRefresh);
connect(mRefresh, &QAction::triggered, [this] {
view()->refresh();
});
@ -315,6 +660,7 @@ MenuBar::MenuBar(QWidget *parent)
viewMenu->addSeparator();
mToggleLog = viewMenu->addAction(tr("Show Log"));
toggleLogHotkey.use(mToggleLog);
connect(mToggleLog, &QAction::triggered, [this] {
RepoView *view = this->view();
view->setLogVisible(!view->isLogVisible());
@ -322,6 +668,7 @@ MenuBar::MenuBar(QWidget *parent)
mToggleMaximize = new StateAction(tr("Normal"), tr("Maximize"), viewMenu);
viewMenu->addAction(mToggleMaximize);
toggleMaximizeHotkey.use(mToggleMaximize);
mToggleMaximize->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));
connect(mToggleMaximize, &QAction::triggered, [this] {
bool maximize = mToggleMaximize->isActive();
@ -336,6 +683,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mToggleView = viewMenu->addAction(tr("Show Tree View"));
toggleViewHotkey.use(mToggleView);
connect(mToggleView, &QAction::triggered, [this] {
RepoView *view = this->view();
bool diff = (view->viewMode() == RepoView::DoubleTree);
@ -346,7 +694,7 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *repository = addMenu(tr("Repository"));
mConfigureRepository = repository->addAction(tr("Configure Repository..."));
mConfigureRepository->setMenuRole(QAction::NoRole);
configureRepositoryHotkey.use(mConfigureRepository);
connect(mConfigureRepository, &QAction::triggered, [this] {
view()->configureSettings();
});
@ -354,13 +702,13 @@ MenuBar::MenuBar(QWidget *parent)
repository->addSeparator();
mStageAll = repository->addAction(tr("Stage All"));
mStageAll->setShortcut(tr("Ctrl++"));
stageAllHotkey.use(mStageAll);
connect(mStageAll, &QAction::triggered, [this] {
view()->stage();
});
mUnstageAll = repository->addAction(tr("Unstage All"));
mUnstageAll->setShortcut(tr("Ctrl+-"));
unstageAllHotkey.use(mUnstageAll);
connect(mUnstageAll, &QAction::triggered, [this] {
view()->unstage();
});
@ -368,13 +716,13 @@ MenuBar::MenuBar(QWidget *parent)
repository->addSeparator();
mCommit = repository->addAction(tr("Commit"));
mCommit->setShortcut(tr("Ctrl+Shift+C"));
commitHotkey.use(mCommit);
connect(mCommit, &QAction::triggered, [this] {
view()->commit();
});
mAmendCommit = repository->addAction(tr("Amend Commit"));
mAmendCommit->setShortcut(tr("Ctrl+Shift+A"));
amendCommitHotkey.use(mAmendCommit);
connect(mAmendCommit, &QAction::triggered, [this] {
view()->amendCommit();
});
@ -383,6 +731,7 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *lfs = repository->addMenu(tr("Git LFS"));
mLfsUnlock = lfs->addAction(tr("Remove all locks"));
lfsUnlockHotkey.use(mLfsUnlock);
connect(mLfsUnlock, &QAction::triggered, [this] {
view()->lfsSetLocked(view()->repo().lfsLocks().values(), false);
});
@ -390,6 +739,7 @@ MenuBar::MenuBar(QWidget *parent)
lfs->addSeparator();
mLfsInitialize = lfs->addAction(tr("Initialize"));
lfsInitializeHotkey.use(mLfsInitialize);
connect(mLfsInitialize, &QAction::triggered, [this] {
view()->lfsInitialize();
});
@ -399,6 +749,7 @@ MenuBar::MenuBar(QWidget *parent)
mConfigureRemotes = remote->addAction(tr("Configure Remotes..."));
mConfigureRemotes->setMenuRole(QAction::NoRole);
configureRemotesHotkey.use(mConfigureRemotes);
connect(mConfigureRemotes, &QAction::triggered, [this] {
view()->configureSettings(ConfigDialog::Remotes);
});
@ -406,19 +757,19 @@ MenuBar::MenuBar(QWidget *parent)
remote->addSeparator();
mFetch = remote->addAction(tr("Fetch"));
mFetch->setShortcut(tr("Ctrl+Shift+Alt+F"));
fetchHotkey.use(mFetch);
connect(mFetch, &QAction::triggered, [this] {
view()->fetch();
});
mFetchAll = remote->addAction(tr("Fetch All"));
mFetchAll->setShortcut(tr("Ctrl+Shift+Alt+A"));
fetchAllHotkey.use(mFetchAll);
connect(mFetchAll, &QAction::triggered, [this] {
view()->fetchAll();
});
mFetchFrom = remote->addAction(tr("Fetch From..."));
mFetchFrom->setShortcut(tr("Ctrl+Shift+F"));
fetchFromHotkey.use(mFetchFrom);
connect(mFetchFrom, &QAction::triggered, [this] {
RemoteDialog *dialog = new RemoteDialog(RemoteDialog::Fetch, view());
dialog->open();
@ -427,13 +778,13 @@ MenuBar::MenuBar(QWidget *parent)
remote->addSeparator();
mPull = remote->addAction(tr("Pull"));
mPull->setShortcut(tr("Ctrl+Shift+Alt+L"));
pullHotkey.use(mPull);
connect(mPull, &QAction::triggered, [this] {
view()->pull();
});
mPullFrom = remote->addAction(tr("Pull From..."));
mPullFrom->setShortcut(tr("Ctrl+Shift+L"));
pullFromHotkey.use(mPullFrom);
connect(mPullFrom, &QAction::triggered, [this] {
RemoteDialog *dialog = new RemoteDialog(RemoteDialog::Pull, view());
dialog->open();
@ -442,13 +793,13 @@ MenuBar::MenuBar(QWidget *parent)
remote->addSeparator();
mPush = remote->addAction(tr("Push"));
mPush->setShortcut(tr("Ctrl+Shift+Alt+P"));
pushHotkey.use(mPush);
connect(mPush, &QAction::triggered, [this] {
view()->push();
});
mPushTo = remote->addAction(tr("Push To..."));
mPushTo->setShortcut(tr("Ctrl+Shift+P"));
pushToHotkey.use(mPushTo);
connect(mPushTo, &QAction::triggered, [this] {
RemoteDialog *dialog = new RemoteDialog(RemoteDialog::Push, view());
dialog->open();
@ -459,11 +810,13 @@ MenuBar::MenuBar(QWidget *parent)
mConfigureBranches = branch->addAction(tr("Configure Branches..."));
mConfigureBranches->setMenuRole(QAction::NoRole);
configureBranchesHotkey.use(mConfigureBranches);
connect(mConfigureBranches, &QAction::triggered, [this] {
view()->configureSettings(ConfigDialog::Branches);
});
mNewBranch = branch->addAction(tr("New Branch..."));
newBranchHotkey.use(mNewBranch);
connect(mNewBranch, &QAction::triggered, [this] {
view()->promptToCreateBranch();
});
@ -471,7 +824,7 @@ MenuBar::MenuBar(QWidget *parent)
branch->addSeparator();
mCheckoutCurrent = branch->addAction(tr("Checkout Current"));
mCheckoutCurrent->setShortcut(tr("Ctrl+Shift+Alt+H"));
checkoutCurrentHotkey.use(mCheckoutCurrent);
connect(mCheckoutCurrent, &QAction::triggered, [this] {
RepoView *view = this->view();
git::Reference ref = view->reference();
@ -482,7 +835,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mCheckout = branch->addAction(tr("Checkout..."));
mCheckout->setShortcut(tr("Ctrl+Shift+H"));
checkoutHotkey.use(mCheckout);
connect(mCheckout, &QAction::triggered, [this] {
view()->promptToCheckout();
});
@ -490,7 +843,7 @@ MenuBar::MenuBar(QWidget *parent)
branch->addSeparator();
mMerge = branch->addAction(tr("Merge..."));
mMerge->setShortcut(tr("Ctrl+Shift+M"));
mergeHotkey.use(mMerge);
connect(mMerge, &QAction::triggered, [this] {
RepoView *view = this->view();
MergeDialog *dialog =
@ -503,7 +856,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mRebase = branch->addAction(tr("Rebase..."));
mRebase->setShortcut(tr("Ctrl+Shift+R"));
rebaseHotkey.use(mRebase);
connect(mRebase, &QAction::triggered, [this] {
RepoView *view = this->view();
MergeDialog *dialog =
@ -516,7 +869,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mSquash = branch->addAction(tr("Squash..."));
mSquash->setShortcut(tr("Ctrl+Shift+Q"));
squashHotkey.use(mSquash);
connect(mSquash, &QAction::triggered, [this] {
RepoView *view = this->view();
MergeDialog *dialog =
@ -531,7 +884,7 @@ MenuBar::MenuBar(QWidget *parent)
branch->addSeparator();
mAbort = branch->addAction(tr("Abort Merge"));
mAbort->setShortcut(tr("Ctrl+Shift+A"));
abortHotkey.use(mAbort);
connect(mAbort, &QAction::triggered, [this] {
view()->mergeAbort();
});
@ -541,6 +894,7 @@ MenuBar::MenuBar(QWidget *parent)
mConfigureSubmodules = submodule->addAction(tr("Configure Submodules..."));
mConfigureSubmodules->setMenuRole(QAction::NoRole);
configureSubmodulesHotkey.use(mConfigureSubmodules);
connect(mConfigureSubmodules, &QAction::triggered, [this] {
view()->configureSettings(ConfigDialog::Submodules);
});
@ -548,13 +902,13 @@ MenuBar::MenuBar(QWidget *parent)
submodule->addSeparator();
mUpdateSubmodules = submodule->addAction(tr("Update All"));
mUpdateSubmodules->setShortcut(tr("Ctrl+Shift+Alt+U"));
updateSubmodulesHotkey.use(mUpdateSubmodules);
connect(mUpdateSubmodules, &QAction::triggered, [this] {
view()->updateSubmodules();
});
mInitSubmodules = submodule->addAction(tr("Update..."));
mInitSubmodules->setShortcut(tr("Ctrl+Shift+U"));
initSubmodulesHotkey.use(mInitSubmodules);
connect(mInitSubmodules, &QAction::triggered, [this] {
RepoView *view = this->view();
git::Repository repo = view->repo();
@ -591,6 +945,7 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *stash = addMenu(tr("Stash"));
mShowStashes = stash->addAction(tr("Show Stashes"));
showStashesHotkey.use(mShowStashes);
connect(mShowStashes, &QAction::triggered, [this] {
RepoView *view = this->view();
view->selectReference(view->repo().stashRef());
@ -599,13 +954,13 @@ MenuBar::MenuBar(QWidget *parent)
stash->addSeparator();
mStash = stash->addAction(tr("Stash..."));
mStash->setShortcut(tr("Ctrl+Shift+T"));
stashHotkey.use(mStash);
connect(mStash, &QAction::triggered, [this] {
view()->promptToStash();
});
mStashPop = stash->addAction(tr("Pop Stash"));
mStashPop->setShortcut(tr("Ctrl+Shift+Alt+T"));
stashPopHotkey.use(mStashPop);
connect(mStashPop, &QAction::triggered, [this] {
view()->popStash();
});
@ -614,14 +969,14 @@ MenuBar::MenuBar(QWidget *parent)
QMenu *historyMenu = addMenu(tr("History"));
mPrev = historyMenu->addAction(tr("Back"));
mPrev->setShortcut(QKeySequence::Back);
prevHotkey.use(mPrev);
mPrev->setEnabled(false);
connect(mPrev, &QAction::triggered, [this] {
view()->history()->prev();
});
mNext = historyMenu->addAction(tr("Forward"));
mNext->setShortcut(QKeySequence::Forward);
nextHotkey.use(mNext);
mNext->setEnabled(false);
connect(mNext, &QAction::triggered, [this] {
view()->history()->next();
@ -630,7 +985,7 @@ MenuBar::MenuBar(QWidget *parent)
// Window
QMenu *windowMenu = addMenu(tr("Window"));
mPrevTab = windowMenu->addAction(tr("Show Previous Tab"));
mPrevTab->setShortcut(QKeySequence::PreviousChild);
prevTabHotkey.use(mPrevTab);
connect(mPrevTab, &QAction::triggered, [this] {
MainWindow *win = static_cast<MainWindow *>(window());
TabWidget *tabs = win->tabWidget();
@ -639,7 +994,7 @@ MenuBar::MenuBar(QWidget *parent)
});
mNextTab = windowMenu->addAction(tr("Show Next Tab"));
mNextTab->setShortcut(QKeySequence::NextChild);
nextTabHotkey.use(mNextTab);
connect(mNextTab, &QAction::triggered, [this] {
MainWindow *win = static_cast<MainWindow *>(window());
TabWidget *tabs = win->tabWidget();
@ -650,13 +1005,13 @@ MenuBar::MenuBar(QWidget *parent)
windowMenu->addSeparator();
QAction *chooser = windowMenu->addAction(tr("Show Repository Chooser..."));
chooser->setShortcut(tr("Ctrl+Shift+O"));
chooserHotkey.use(chooser);
connect(chooser, &QAction::triggered, &StartDialog::openSharedInstance);
// Tools
QMenu *tools = addMenu(tr("Tools"));
QAction *preferences = tools->addAction(tr("Options..."));
preferences->setMenuRole(QAction::PreferencesRole);
preferencesHotkey.use(preferences);
connect(preferences, &QAction::triggered, [] {
SettingsDialog::openSharedInstance();
});

View File

@ -16,6 +16,7 @@
#include "dialogs/PullRequestDialog.h"
#include "git/Branch.h"
#include "git/Commit.h"
#include "ui/HotkeyManager.h"
#include <QAction>
#include <QButtonGroup>
#include <QHBoxLayout>
@ -801,6 +802,18 @@ public:
}
};
static Hotkey terminalHotkey = HotkeyManager::registerHotkey(
nullptr,
"tools/terminal",
"Tools/Open Terminal"
);
static Hotkey fileManagerHotkey = HotkeyManager::registerHotkey(
nullptr,
"tools/fileManager",
"Tools/Open File Manager"
);
} // anon. namespace
ToolBar::ToolBar(MainWindow *parent)
@ -948,7 +961,13 @@ ToolBar::ToolBar(MainWindow *parent)
connect(mTerminalButton, &Button::clicked, [this] {
currentView()->openTerminal();
});
QShortcut *shortcut = new QShortcut(this);
terminalHotkey.use(shortcut);
connect(shortcut, &QShortcut::activated, mTerminalButton, &Button::click);
addWidget(new Spacer(4, this));
mFileManagerButton = new FileManagerButton(this);
mFileManagerButton->setToolTip(tr("Open file manager"));
addWidget(mFileManagerButton);
@ -956,6 +975,10 @@ ToolBar::ToolBar(MainWindow *parent)
currentView()->openFileManager();
});
shortcut = new QShortcut(this);
fileManagerHotkey.use(shortcut);
connect(shortcut, &QShortcut::activated, mFileManagerButton, &Button::click);
addWidget(new Spacer(4, this));
mConfigButton = new SettingsButton(this);