Add new plugin QuickCommands

This plugin can save your favourite commands to sidebar
You can double click the item to invoke the command

Signed-off-by: Tao Guo <guotao945@gmail.com>
This commit is contained in:
Tao Guo
2022-02-03 09:14:29 +08:00
committed by Tao Guo
parent 06726d8a38
commit 365a529881
12 changed files with 719 additions and 0 deletions

View File

@@ -112,6 +112,7 @@ include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} )
file(GLOB ICONS_SRCS "data/icons/*.png")
option(ENABLE_PLUGIN_SSHMANAGER "Build the SSHManager plugin" ON)
option(ENABLE_PLUGIN_QUICKCOMMANDS "Build the Quick Commands plugin" ON)
add_subdirectory( src )
add_subdirectory( data )

View File

@@ -1,3 +1,6 @@
if(ENABLE_PLUGIN_SSHMANAGER)
add_subdirectory(SSHManager)
endif()
if(ENABLE_PLUGIN_QUICKCOMMANDS)
add_subdirectory(QuickCommands)
endif()

View File

@@ -0,0 +1,17 @@
ki18n_wrap_ui(EXTRA_QUICKCOMMANDSPLUGIN_SRCS qcwidget.ui)
kcoreaddons_add_plugin(konsole_quickcommandsplugin
SOURCES
quickcommandsplugin.cpp
quickcommandswidget.cpp
quickcommandsmodel.cpp
${EXTRA_QUICKCOMMANDSPLUGIN_SRCS}
INSTALL_NAMESPACE
"konsoleplugins"
)
target_link_libraries(konsole_quickcommandsplugin
konsoleprivate
konsoleapp
)

View File

@@ -0,0 +1,36 @@
{
"KPlugin": {
"Name": "Quick Commands",
"Name[ar]": "Quick Commands",
"Name[az]": "Quick Commands",
"Name[ca@valencia]": "Quick Commands",
"Name[ca]": "Quick Commands",
"Name[cs]": "Quick Commands",
"Name[de]": "Quick Commands",
"Name[el]": "Quick Commands",
"Name[en_GB]": "Quick Commands",
"Name[es]": "Quick Commands",
"Name[eu]": "Quick Commands",
"Name[fi]": "Quick Commands",
"Name[fr]": "Quick Commands",
"Name[hu]": "Quick Commands",
"Name[ia]": "Quick Commands",
"Name[it]": "Quick Commands",
"Name[ko]": "Quick Commands",
"Name[nl]": "Quick Commands",
"Name[nn]": "Quick Commands",
"Name[pl]": "Quick Commands",
"Name[pt]": "Quick Commands",
"Name[pt_BR]": "Quick Commands",
"Name[ro]": "Quick Commands",
"Name[ru]": "Quick Commands",
"Name[sk]": "Quick Commands",
"Name[sl]": "Quick Commands",
"Name[sv]": "Quick Commands",
"Name[ta]": "Quick Commands",
"Name[tr]": "Quick Commands",
"Name[uk]": "Quick Commands",
"Name[x-test]": "xxQuick Commandsxx",
"Name[zh_CN]": "快捷指令"
}
}

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QuickCommandsWidget</class>
<widget class="QWidget" name="QuickCommandsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>424</width>
<height>474</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="commandsTreeView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="commandsWidget" native="true">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Caption</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelGroup">
<property name="text">
<string>Group</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Command</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPlainTextEdit" name="command">
<property name="plainText">
<string notr="true"/>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="group">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>Default</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnAdd">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnUpdate">
<property name="text">
<string>Update</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,19 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef QUICKCOMMANDDATA_H
#define QUICKCOMMANDDATA_H
#include <QString>
class QuickCommandData
{
public:
QString name;
QString command;
};
Q_DECLARE_METATYPE(QuickCommandData)
#endif // QUICKCOMMANDDATA_H

View File

@@ -0,0 +1,131 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "quickcommandsmodel.h"
#include "quickcommanddata.h"
#include <KConfig>
#include <KConfigGroup>
QuickCommandsModel::QuickCommandsModel(QObject *parent)
: QStandardItemModel(parent)
{
load();
}
QuickCommandsModel::~QuickCommandsModel() noexcept
{
save();
}
void QuickCommandsModel::load()
{
auto config = KConfig(QStringLiteral("konsolequickcommandsconfig"), KConfig::OpenFlag::SimpleConfig);
for (const QString &groupName : config.groupList()) {
KConfigGroup group = config.group(groupName);
addTopLevelItem(groupName);
for (const QString &commandGroup : group.groupList()) {
QuickCommandData data;
KConfigGroup element = group.group(commandGroup);
data.name = element.readEntry("name");
data.command = element.readEntry("command");
addChildItem(data, groupName);
}
}
}
void QuickCommandsModel::save()
{
auto config = KConfig(QStringLiteral("konsolequickcommandsconfig"), KConfig::OpenFlag::SimpleConfig);
for (const QString &groupName : config.groupList()) {
config.deleteGroup(groupName);
}
for (int i = 0, end = invisibleRootItem()->rowCount(); i < end; i++) {
QStandardItem *groupItem = invisibleRootItem()->child(i);
const QString groupName = groupItem->text();
KConfigGroup baseGroup = config.group(groupName);
for (int j = 0, end2 = groupItem->rowCount(); j < end2; j++) {
QStandardItem *item = groupItem->child(j);
const auto data = item->data(QuickCommandRole).value<QuickCommandData>();
KConfigGroup element = baseGroup.group(data.name.trimmed());
element.writeEntry("name", data.name);
element.writeEntry("command", data.command);
element.sync();
}
baseGroup.sync();
}
config.sync();
}
QStringList QuickCommandsModel::groups() const
{
QStringList retList;
for (int i = 0, end = invisibleRootItem()->rowCount(); i < end; i++) {
retList.push_back(invisibleRootItem()->child(i)->text());
}
return retList;
}
QStandardItem *QuickCommandsModel::addTopLevelItem(const QString &groupName)
{
for (int i = 0, end = invisibleRootItem()->rowCount(); i < end; i++) {
if (invisibleRootItem()->child(i)->text() == groupName) {
return nullptr;
}
}
auto *newItem = new QStandardItem();
newItem->setText(groupName);
invisibleRootItem()->appendRow(newItem);
invisibleRootItem()->sortChildren(0);
return newItem;
}
bool QuickCommandsModel::addChildItem(const QuickCommandData &data, const QString &groupName)
{
QStandardItem *parentItem = nullptr;
for (int i = 0, end = invisibleRootItem()->rowCount(); i < end; i++) {
if (invisibleRootItem()->child(i)->text() == groupName) {
parentItem = invisibleRootItem()->child(i);
break;
}
}
if (!parentItem)
parentItem = addTopLevelItem(groupName);
for (int i = 0, end = parentItem->rowCount(); i < end; i++) {
if (parentItem->child(i)->text() == data.name)
return false;
}
auto newChild = new QStandardItem();
newChild->setData(QVariant::fromValue(data), QuickCommandRole);
newChild->setText(data.name);
newChild->setToolTip(data.command);
parentItem->appendRow(newChild);
parentItem->sortChildren(0);
return true;
}
bool QuickCommandsModel::editChildItem(const QuickCommandData &data, const QModelIndex &idx, const QString &groupName)
{
QStandardItem *item = itemFromIndex(idx);
QStandardItem *parentItem = item->parent();
for (int i = 0, end = parentItem->rowCount(); i < end; i++) {
if (parentItem->child(i)->text() == data.name && parentItem->child(i) != item)
return false;
}
if (groupName != parentItem->text()) {
if (!addChildItem(data, groupName))
return false;
parentItem->removeRow(item->row());
} else {
item->setData(QVariant::fromValue(data), QuickCommandRole);
item->setText(data.name);
item->setToolTip(data.command);
item->parent()->sortChildren(0);
}
return true;
}

View File

@@ -0,0 +1,30 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef QUICKCOMMANDSMODEL_H
#define QUICKCOMMANDSMODEL_H
#include <qstandarditemmodel.h>
class QuickCommandData;
class QuickCommandsModel : public QStandardItemModel
{
Q_OBJECT
public:
enum Roles { QuickCommandRole = Qt::UserRole + 1 };
QuickCommandsModel(QObject *parent = nullptr);
~QuickCommandsModel() override;
QStringList groups() const;
bool addChildItem(const QuickCommandData &data, const QString &groupName);
bool editChildItem(const QuickCommandData &data, const QModelIndex &idx, const QString &groupName);
private:
void load();
void save();
QStandardItem *addTopLevelItem(const QString &groupName);
};
#endif // QUICKCOMMANDSMODEL_H

View File

@@ -0,0 +1,66 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "quickcommandsplugin.h"
#include "konsoledebug.h"
#include "quickcommandsmodel.h"
#include "quickcommandswidget.h"
#include <KActionCollection>
#include <QMainWindow>
#include "MainWindow.h"
K_PLUGIN_CLASS_WITH_JSON(QuickCommandsPlugin, "konsole_quickcommands.json")
struct QuickCommandsPlugin::Private {
QuickCommandsModel model;
QMap<Konsole::MainWindow *, QuickCommandsWidget *> widgetForWindow;
QMap<Konsole::MainWindow *, QDockWidget *> dockForWindow;
};
QuickCommandsPlugin::QuickCommandsPlugin(QObject *object, const QVariantList &args)
: Konsole::IKonsolePlugin(object, args)
, priv(std::make_unique<Private>())
{
setName(QStringLiteral("QuickCommands"));
}
QuickCommandsPlugin::~QuickCommandsPlugin() = default;
void QuickCommandsPlugin::createWidgetsForMainWindow(Konsole::MainWindow *mainWindow)
{
auto *qcDockWidget = new QDockWidget(mainWindow);
auto *qcWidget = new QuickCommandsWidget(mainWindow);
qcWidget->setModel(&priv->model);
qcDockWidget->setWindowTitle(i18n("Quick Commands"));
qcDockWidget->setWidget(qcWidget);
qcDockWidget->setObjectName(QStringLiteral("QuickCommandsDock"));
qcDockWidget->setVisible(true);
mainWindow->addDockWidget(Qt::LeftDockWidgetArea, qcDockWidget);
priv->widgetForWindow[mainWindow] = qcWidget;
priv->dockForWindow[mainWindow] = qcDockWidget;
}
void QuickCommandsPlugin::activeViewChanged(Konsole::SessionController *controller, Konsole::MainWindow *mainWindow)
{
if (mainWindow)
priv->widgetForWindow[mainWindow]->setCurrentController(controller);
}
QList<QAction *> QuickCommandsPlugin::menuBarActions(Konsole::MainWindow *mainWindow) const
{
QAction *toggleVisibilityAction = new QAction(i18n("Show Quick Commands"), mainWindow);
toggleVisibilityAction->setCheckable(true);
connect(toggleVisibilityAction, &QAction::triggered, priv->dockForWindow[mainWindow], &QDockWidget::setVisible);
connect(priv->dockForWindow[mainWindow], &QDockWidget::visibilityChanged, toggleVisibilityAction, &QAction::setChecked);
return {toggleVisibilityAction};
}
#include "quickcommandsplugin.moc"

View File

@@ -0,0 +1,33 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef QUICKCOMMANDSPLUGIN_H
#define QUICKCOMMANDSPLUGIN_H
#include <pluginsystem/IKonsolePlugin.h>
namespace Konsole
{
class SessionController;
class MainWindow;
}
class QuickCommandsPlugin : public Konsole::IKonsolePlugin
{
Q_OBJECT
public:
QuickCommandsPlugin(QObject *object, const QVariantList &args);
~QuickCommandsPlugin() override;
void createWidgetsForMainWindow(Konsole::MainWindow *mainWindow) override;
void activeViewChanged(Konsole::SessionController *controller, Konsole::MainWindow *mainWindow) override;
QList<QAction *> menuBarActions(Konsole::MainWindow *mainWindow) const override;
private:
struct Private;
std::unique_ptr<Private> priv;
};
#endif // QUICKCOMMANDSPLUGIN_H

View File

@@ -0,0 +1,197 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "quickcommandswidget.h"
#include "konsoledebug.h"
#include "quickcommandsmodel.h"
#include "session/SessionController.h"
#include "terminalDisplay/TerminalDisplay.h"
#include <QMenu>
#include <QSortFilterProxyModel>
#include "ui_qcwidget.h"
#include <KMessageBox>
struct QuickCommandsWidget::Private {
QuickCommandsModel *model = nullptr;
QSortFilterProxyModel *filterModel = nullptr;
Konsole::SessionController *controller = nullptr;
};
QuickCommandsWidget::QuickCommandsWidget(QWidget *parent)
: QWidget(parent)
, ui(std::make_unique<Ui::QuickCommandsWidget>())
, priv(std::make_unique<Private>())
{
ui->setupUi(this);
priv->filterModel = new QSortFilterProxyModel(this);
connect(ui->btnAdd, &QPushButton::clicked, this, &QuickCommandsWidget::addMode);
connect(ui->btnSave, &QPushButton::clicked, this, &QuickCommandsWidget::saveCommand);
connect(ui->btnUpdate, &QPushButton::clicked, this, &QuickCommandsWidget::updateCommand);
connect(ui->btnCancel, &QPushButton::clicked, this, &QuickCommandsWidget::viewMode);
ui->commandsTreeView->setModel(priv->filterModel);
ui->commandsTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->commandsTreeView, &QTreeView::doubleClicked, this, &QuickCommandsWidget::invokeCommand);
connect(ui->commandsTreeView, &QTreeView::customContextMenuRequested, [this](const QPoint &pos) {
QModelIndex idx = ui->commandsTreeView->indexAt(pos);
if (!idx.isValid())
return;
auto sourceIdx = priv->filterModel->mapToSource(idx);
const bool isParent = sourceIdx.parent() == priv->model->invisibleRootItem()->index();
QMenu *menu = new QMenu(this);
if (!isParent) {
auto actionEdit = new QAction(QStringLiteral("Edit"), ui->commandsTreeView);
menu->addAction(actionEdit);
connect(actionEdit, &QAction::triggered, this, &QuickCommandsWidget::triggerEdit);
} else {
auto actionRename = new QAction(QStringLiteral("Rename"), ui->commandsTreeView);
menu->addAction(actionRename);
connect(actionRename, &QAction::triggered, this, &QuickCommandsWidget::triggerRename);
}
auto actionDelete = new QAction(QStringLiteral("Delete"), ui->commandsTreeView);
menu->addAction(actionDelete);
connect(actionDelete, &QAction::triggered, this, &QuickCommandsWidget::triggerDelete);
menu->popup(ui->commandsTreeView->viewport()->mapToGlobal(pos));
});
viewMode();
}
QuickCommandsWidget::~QuickCommandsWidget() = default;
void QuickCommandsWidget::prepareEdit()
{
QString groupName = ui->group->currentText();
ui->group->clear();
ui->group->addItems(priv->model->groups());
ui->group->setCurrentText(groupName);
ui->commandsTreeView->setDisabled(true);
ui->commandsWidget->show();
}
void QuickCommandsWidget::viewMode()
{
ui->commandsTreeView->setDisabled(false);
ui->commandsWidget->hide();
ui->btnAdd->show();
ui->btnSave->hide();
ui->btnUpdate->hide();
ui->btnCancel->hide();
}
void QuickCommandsWidget::addMode()
{
ui->command->setPlainText({});
ui->name->setText({});
ui->btnAdd->hide();
ui->btnSave->show();
ui->btnUpdate->hide();
ui->btnCancel->show();
prepareEdit();
}
void QuickCommandsWidget::editMode()
{
ui->btnAdd->hide();
ui->btnSave->hide();
ui->btnUpdate->show();
ui->btnCancel->show();
prepareEdit();
}
void QuickCommandsWidget::saveCommand()
{
if (priv->model->addChildItem(data(), ui->group->currentText()))
viewMode();
else
KMessageBox::messageBox(this, KMessageBox::DialogType::Error, i18n("A duplicate name exists"));
}
void QuickCommandsWidget::updateCommand()
{
const auto sourceIdx = priv->filterModel->mapToSource(ui->commandsTreeView->currentIndex());
if (priv->model->editChildItem(data(), sourceIdx, ui->group->currentText()))
viewMode();
else
KMessageBox::messageBox(this, KMessageBox::DialogType::Error, i18n("A duplicate name exists"));
}
void QuickCommandsWidget::invokeCommand(const QModelIndex &idx)
{
if (!priv->controller) {
return;
}
const auto sourceIdx = priv->filterModel->mapToSource(idx);
if (sourceIdx.parent() == priv->model->invisibleRootItem()->index()) {
return;
}
const auto item = priv->model->itemFromIndex(sourceIdx);
const auto data = item->data(QuickCommandsModel::QuickCommandRole).value<QuickCommandData>();
priv->controller->session()->sendTextToTerminal(data.command, QLatin1Char('\r'));
if (priv->controller->session()->views().count()) {
priv->controller->session()->views().at(0)->setFocus();
}
}
void QuickCommandsWidget::triggerEdit()
{
const auto sourceIdx = priv->filterModel->mapToSource(ui->commandsTreeView->currentIndex());
const auto item = priv->model->itemFromIndex(sourceIdx);
const auto data = item->data(QuickCommandsModel::QuickCommandRole).value<QuickCommandData>();
ui->name->setText(data.name);
ui->command->setPlainText(data.command);
ui->group->setCurrentText(item->parent()->text());
editMode();
}
void QuickCommandsWidget::triggerRename()
{
ui->commandsTreeView->edit(ui->commandsTreeView->currentIndex());
}
void QuickCommandsWidget::triggerDelete()
{
const auto idx = ui->commandsTreeView->currentIndex();
const QString text = idx.data(Qt::DisplayRole).toString();
const QString dialogMessage = ui->commandsTreeView->model()->rowCount(idx)
? i18n("You are about to remove the group %1,\n with multiple configurations, are you sure?", text)
: i18n("You are about to remove %1, are you sure?", text);
KMessageBox::ButtonCode result = KMessageBox::messageBox(this,
KMessageBox::DialogType::WarningYesNo,
dialogMessage,
i18n("Remove Quick Commands Configurations"),
KStandardGuiItem::yes(),
KStandardGuiItem::no(),
KStandardGuiItem::cancel());
if (result != KMessageBox::ButtonCode::Yes)
return;
const auto sourceIdx = priv->filterModel->mapToSource(idx);
priv->model->removeRow(sourceIdx.row(), sourceIdx.parent());
}
QuickCommandData QuickCommandsWidget::data() const
{
QuickCommandData data;
data.name = ui->name->text().trimmed();
data.command = ui->command->toPlainText();
return data;
}
void QuickCommandsWidget::setModel(QuickCommandsModel *model)
{
priv->model = model;
priv->filterModel->setSourceModel(model);
}
void QuickCommandsWidget::setCurrentController(Konsole::SessionController *controller)
{
priv->controller = controller;
}

View File

@@ -0,0 +1,58 @@
// This file was part of the KDE libraries
// SPDX-FileCopyrightText: 2022 Tao Guo <guotao945@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef QUICKCOMMANDSWIDGET_H
#define QUICKCOMMANDSWIDGET_H
#include <QWidget>
#include <memory>
#include "quickcommanddata.h"
#include "quickcommandsmodel.h"
#include "session/SessionController.h"
namespace Ui
{
class QuickCommandsWidget;
}
class QuickCommandsWidget : public QWidget
{
Q_OBJECT
public:
QuickCommandsWidget(QWidget *parent = nullptr);
~QuickCommandsWidget() override;
Q_SLOT void viewMode();
Q_SLOT void addMode();
Q_SLOT void editMode();
Q_SLOT void saveCommand();
Q_SLOT void updateCommand();
Q_SLOT void invokeCommand(const QModelIndex &idx);
Q_SLOT void triggerEdit();
Q_SLOT void triggerRename();
Q_SLOT void triggerDelete();
void setModel(QuickCommandsModel *model);
void setCurrentController(Konsole::SessionController *controller);
private:
QuickCommandData data() const;
void prepareEdit();
struct Private;
std::unique_ptr<Ui::QuickCommandsWidget> ui;
std::unique_ptr<Private> priv;
};
#endif // QUICKCOMMANDSWIDGET_H