Commit a592691c authored by Pablo Rauzy's avatar Pablo Rauzy Committed by Christoph Cullmann
Browse files

added support for persistent named keyboard macros

parent c41bfcb6
......@@ -8,14 +8,26 @@
#include <QAction>
#include <QApplication>
#include <QCoreApplication>
#include <QFile>
#include <QIODevice>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QKeyEvent>
#include <QKeySequence>
#include <QList>
#include <QRegExp>
#include <QStandardPaths>
#include <QString>
#include <QtAlgorithms>
#include <QStringList>
#include <KTextEditor/Application>
#include <KTextEditor/Command>
#include <KTextEditor/Editor>
#include <KTextEditor/MainWindow>
#include <KTextEditor/Message>
#include <KTextEditor/Plugin>
#include <KTextEditor/View>
#include <KActionCollection>
#include <KLocalizedString>
......@@ -27,10 +39,15 @@ K_PLUGIN_FACTORY_WITH_JSON(KeyboardMacrosPluginFactory, "keyboardmacrosplugin.js
KeyboardMacrosPlugin::KeyboardMacrosPlugin(QObject *parent, const QList<QVariant> &)
: KTextEditor::Plugin(parent)
{
m_commands = new KeyboardMacrosPluginCommands(this);
m_storage = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kate/keyboardmacros.json");
loadNamedMacros();
}
KeyboardMacrosPlugin::~KeyboardMacrosPlugin()
{
saveNamedMacros();
delete m_commands;
}
QObject *KeyboardMacrosPlugin::createView(KTextEditor::MainWindow *mainWindow)
......@@ -151,6 +168,81 @@ bool KeyboardMacrosPlugin::play()
return true;
}
bool KeyboardMacrosPlugin::save(QString name)
{
// we don't need to save macros that do nothing
if (m_macro.isEmpty()) {
return false;
}
qDebug() << "[KeyboardMacrosPlugin] saving macro:" << name;
m_namedMacros.insert(name, m_macro);
return true;
}
bool KeyboardMacrosPlugin::load(QString name)
{
if (!m_namedMacros.contains(name)) {
return false;
}
qDebug() << "[KeyboardMacrosPlugin] loading macro:" << name;
// clear current macro
m_macro.clear();
// load named macro
m_macro = m_namedMacros.value(name);
// update GUI
m_playAction->setEnabled(true);
return true;
}
bool KeyboardMacrosPlugin::remove(QString name)
{
if (!m_namedMacros.contains(name)) {
return false;
}
qDebug() << "[KeyboardMacrosPlugin] removing macro:" << name;
m_namedMacros.remove(name);
return true;
}
void KeyboardMacrosPlugin::loadNamedMacros()
{
QFile storage(m_storage);
if (!storage.open(QIODevice::ReadOnly | QIODevice::Text)) {
sendMessage(i18n("Could not open file '%1'.", m_storage), false);
return;
}
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(storage.readAll(), &parseError);
if (parseError.error != QJsonParseError::NoError) {
sendMessage(i18n("Malformed JSON file '%1': %2", m_storage, parseError.errorString()), true);
}
QJsonObject json = jsonDoc.object();
QJsonObject::ConstIterator it;
for (it = json.constBegin(); it != json.constEnd(); ++it) {
m_namedMacros.insert(it.key(), Macro(it.value()));
}
storage.close();
}
void KeyboardMacrosPlugin::saveNamedMacros()
{
if (m_namedMacros.isEmpty()) {
return;
}
QFile storage(m_storage);
if (!storage.open(QIODevice::WriteOnly | QIODevice::Text)) {
sendMessage(i18n("Could not open file '%1'.", m_storage), false);
return;
}
QJsonObject json;
QMap<QString, Macro>::ConstIterator it;
for (it = m_namedMacros.constBegin(); it != m_namedMacros.constEnd(); ++it) {
json.insert(it.key(), it.value().toJson());
}
storage.write(QJsonDocument(json).toJson(QJsonDocument::Compact));
storage.close();
}
void KeyboardMacrosPlugin::focusObjectChanged(QObject *focusObject)
{
qDebug() << "[KeyboardMacrosPlugin] focusObjectChanged:" << focusObject;
......@@ -256,5 +348,61 @@ KeyboardMacrosPluginView::~KeyboardMacrosPluginView()
// END
// BEGIN Plugin commands to manage named keyboard macros
KeyboardMacrosPluginCommands::KeyboardMacrosPluginCommands(KeyboardMacrosPlugin *plugin)
: KTextEditor::Command(QStringList() << QStringLiteral("kmsave") << QStringLiteral("kmload") << QStringLiteral("kmremove"), plugin)
, m_plugin(plugin)
{
}
bool KeyboardMacrosPluginCommands::exec(KTextEditor::View *, const QString &cmd, QString &msg, const KTextEditor::Range &)
{
QStringList actionAndName = cmd.split(QRegExp(QStringLiteral("\\s+")));
if (actionAndName.length() != 2) {
msg = i18n("Usage: %1 <name>.", actionAndName.at(0));
return false;
}
QString action = actionAndName.at(0);
QString name = actionAndName.at(1);
if (action == QStringLiteral("kmsave")) {
if (!m_plugin->save(name)) {
msg = i18n("Cannot save empty keyboard macro.");
return false;
}
return true;
} else if (action == QStringLiteral("kmload")) {
if (!m_plugin->load(name)) {
msg = i18n("No keyboard macro named '%1' found.", name);
return false;
}
return true;
} else if (action == QStringLiteral("kmremove")) {
if (!m_plugin->remove(name)) {
msg = i18n("No keyboard macro named '%1' found.", name);
return false;
}
return true;
}
return false;
}
bool KeyboardMacrosPluginCommands::help(KTextEditor::View *, const QString &cmd, QString &msg)
{
if (cmd == QStringLiteral("kmsave")) {
msg = i18n("<qt><p>Usage: <code>kmsave &lt;name&gt;</code></p><p>Save current keyboard macro as <code>&lt;name&gt;</code>.</p></qt>");
return true;
} else if (cmd == QStringLiteral("kmload")) {
msg = i18n("<qt><p>Usage: <code>kmload &lt;name&gt;</code></p><p>Load saved keyboard macro <code>&lt;name&gt;</code> as current macro.</p></qt>");
return true;
} else if (cmd == QStringLiteral("kmremove")) {
msg = i18n("<qt><p>Usage: <code>kmremove &lt;name&gt;</code></p><p>Remove saved keyboard macro <code>&lt;name&gt;</code>.</p></qt>");
return true;
}
return false;
}
// END
// required for KeyboardMacrosPluginFactory vtable
#include "keyboardmacrosplugin.moc"
......@@ -3,11 +3,16 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef PLUGIN_KATEKEYBOARDMACRO_H
#define PLUGIN_KATEKEYBOARDMACRO_H
#ifndef KEYBOARDMACROSPLUGIN_H
#define KEYBOARDMACROSPLUGIN_H
#include <QKeyEvent>
#include <QEvent>
#include <QList>
#include <QMap>
#include <QObject>
#include <QString>
#include <QVariant>
#include <QVariantMap>
#include <KTextEditor/Application>
#include <KTextEditor/Command>
......@@ -15,39 +20,17 @@
#include <KTextEditor/Plugin>
#include <KTextEditor/View>
class KeyCombination
{
private:
int key;
Qt::KeyboardModifiers modifiers;
QString text;
#include "macro.h"
public:
explicit KeyCombination(const QKeyEvent *keyEvent)
: key(keyEvent->key())
, modifiers(keyEvent->modifiers())
, text(keyEvent->text()){};
QKeyEvent *keyPress()
{
return new QKeyEvent(QEvent::KeyPress, key, modifiers, text);
};
QKeyEvent *keyRelease()
{
return new QKeyEvent(QEvent::KeyRelease, key, modifiers, text);
};
friend QDebug operator<<(QDebug dbg, const KeyCombination &kc)
{
return dbg << QKeySequence(kc.key | kc.modifiers).toString();
};
};
typedef QList<KeyCombination> Macro;
class KeyboardMacrosPluginView;
class KeyboardMacrosPluginCommands;
class KeyboardMacrosPlugin : public KTextEditor::Plugin
{
Q_OBJECT
friend class KeyboardMacrosPluginView;
friend KeyboardMacrosPluginView;
friend KeyboardMacrosPluginCommands;
public:
explicit KeyboardMacrosPlugin(QObject *parent = nullptr, const QList<QVariant> & = QList<QVariant>());
......@@ -68,19 +51,30 @@ private:
KTextEditor::MainWindow *m_mainWindow = nullptr;
QWidget *m_focusWidget = nullptr;
KeyboardMacrosPluginCommands *m_commands;
QAction *m_recordAction = nullptr;
QAction *m_cancelAction = nullptr;
QAction *m_playAction = nullptr;
bool m_recording = false;
Macro m_tape;
Macro m_macro;
void record();
void stop(bool save);
void cancel();
bool play();
bool save(QString name);
bool load(QString name);
bool remove(QString name);
bool m_recording = false;
Macro m_tape;
Macro m_macro;
QMap<QString, Macro> m_namedMacros;
QString m_storage;
void loadNamedMacros();
void saveNamedMacros();
void focusObjectChanged(QObject *focusObject);
void applicationStateChanged(Qt::ApplicationState state);
......@@ -91,7 +85,7 @@ public Q_SLOTS:
};
/**
* Plugin view to add our actions to the gui
* Plugin view to add keyboard macros actions to the GUI
*/
class KeyboardMacrosPluginView : public QObject, public KXMLGUIClient
{
......@@ -105,4 +99,20 @@ private:
KTextEditor::MainWindow *m_mainWindow;
};
/**
* Plugin commands to manage named keyboard macros
*/
class KeyboardMacrosPluginCommands : public KTextEditor::Command
{
Q_OBJECT
public:
KeyboardMacrosPluginCommands(KeyboardMacrosPlugin *plugin);
bool exec(KTextEditor::View *, const QString &cmd, QString &msg, const KTextEditor::Range & = KTextEditor::Range::invalid()) override;
bool help(KTextEditor::View *, const QString &cmd, QString &msg) override;
private:
KeyboardMacrosPlugin *m_plugin;
};
#endif
/*
* SPDX-FileCopyrightText: 2022 Pablo Rauzy <r .at. uzy .dot. me>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KEYBOARDMACROSPLUGIN_KEYCOMBINATION_H
#define KEYBOARDMACROSPLUGIN_KEYCOMBINATION_H
#include <QDebug>
#include <QJsonArray>
#include <QJsonValue>
#include <QKeyEvent>
#include <QKeySequence>
#include <QList>
#include <QString>
#include <QtGlobal>
class KeyCombination
{
private:
int key;
Qt::KeyboardModifiers modifiers;
QString text;
public:
explicit KeyCombination(const QKeyEvent *keyEvent)
: key(keyEvent->key())
, modifiers(keyEvent->modifiers())
, text(keyEvent->text()){};
explicit KeyCombination(const QJsonArray json)
{
Q_ASSERT(json.size() == 3);
Q_ASSERT(json.at(0).type() == QJsonValue::Double);
Q_ASSERT(json.at(1).type() == QJsonValue::Double);
Q_ASSERT(json.at(2).type() == QJsonValue::String);
key = json.at(0).toInt(0);
modifiers = static_cast<Qt::KeyboardModifiers>(json.at(1).toInt(0));
text = json.at(2).toString();
};
QKeyEvent *keyPress()
{
return new QKeyEvent(QEvent::KeyPress, key, modifiers, text);
};
QKeyEvent *keyRelease()
{
return new QKeyEvent(QEvent::KeyRelease, key, modifiers, text);
};
QJsonArray toJson() const
{
QJsonArray json;
json.append(QJsonValue(key));
json.append(QJsonValue(static_cast<int>(modifiers)));
json.append(QJsonValue(text));
return json;
};
friend QDebug operator<<(QDebug dbg, const KeyCombination &kc)
{
return dbg << QKeySequence(kc.key | kc.modifiers).toString();
};
};
#endif
/*
* SPDX-FileCopyrightText: 2022 Pablo Rauzy <r .at. uzy .dot. me>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KEYBOARDMACROSPLUGIN_MACRO_H
#define KEYBOARDMACROSPLUGIN_MACRO_H
#include <QDebug>
#include <QJsonArray>
#include <QJsonValue>
#include <QList>
#include <QtGlobal>
#include "keycombination.h"
class Macro : public QList<KeyCombination>
{
public:
explicit Macro()
: QList<KeyCombination>(){};
explicit Macro(const QJsonValue json)
{
Q_ASSERT(json.type() == QJsonValue::Array);
QJsonArray::ConstIterator it;
for (it = json.toArray().constBegin(); it != json.toArray().constEnd(); ++it) {
Q_ASSERT(it->type() == QJsonValue::Array);
this->append(KeyCombination(it->toArray()));
}
};
QJsonArray toJson() const
{
QJsonArray json;
Macro::ConstIterator it;
for (it = this->constBegin(); it != this->constEnd(); ++it) {
json.append(it->toJson());
}
return json;
};
};
#endif
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment