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

the plugin has grown: refactoring with one class per file

parent b8782f2b
Pipeline #217897 passed with stage
in 5 minutes and 6 seconds
......@@ -6,5 +6,7 @@ target_sources(
keyboardmacrosplugin
PRIVATE
keyboardmacrosplugin.cpp
keyboardmacrospluginview.cpp
keyboardmacrosplugincommands.cpp
plugin.qrc
)
......@@ -3,49 +3,39 @@
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "keyboardmacrosplugin.h"
#include <QAction>
#include <QApplication>
#include <QCompleter>
#include <QCoreApplication>
#include <QDebug>
#include <QDialog>
#include <QFile>
#include <QIODevice>
#include <QInputDialog>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QKeyEvent>
#include <QKeySequence>
#include <QLineEdit>
#include <QList>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QObject>
#include <QPointer>
#include <QRegularExpression>
#include <QSaveFile>
#include <QStandardPaths>
#include <QString>
#include <QStringList>
#include <KActionCollection>
#include <KActionMenu>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KStringHandler>
#include <KXMLGUIFactory>
#include <KTextEditor/Application>
#include <KTextEditor/Command>
#include <KTextEditor/Editor>
#include <KTextEditor/MainWindow>
#include <KTextEditor/Message>
#include <KTextEditor/Plugin>
#include <KTextEditor/View>
#include "keyboardmacrosplugin.h"
#include "keyboardmacrosplugincommands.h"
#include "keyboardmacrospluginview.h"
#include "keycombination.h"
#include "macro.h"
......@@ -374,395 +364,5 @@ bool KeyboardMacrosPlugin::wipe(const QString &name)
// END
// BEGIN Plugin view to add our actions to the GUI
KeyboardMacrosPluginView::KeyboardMacrosPluginView(KeyboardMacrosPlugin *plugin, KTextEditor::MainWindow *mainwindow)
: QObject(mainwindow)
, m_plugin(plugin)
, m_mainWindow(mainwindow)
{
// setup XML GUI
KXMLGUIClient::setComponentName(QStringLiteral("keyboardmacros"), i18n("Keyboard Macros"));
setXMLFile(QStringLiteral("ui.rc"));
KActionMenu *menu = new KActionMenu(i18n("&Keyboard Macros"), this);
menu->setIcon(QIcon::fromTheme(QStringLiteral("input-keyboard")));
actionCollection()->addAction(QStringLiteral("keyboardmacros"), menu);
menu->setToolTip(i18n("Record and play keyboard macros."));
menu->setEnabled(true);
// create record action
m_recordAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_record"));
m_recordAction->setText(i18n("&Record Macro..."));
m_recordAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
m_recordAction->setToolTip(i18n("Start/stop recording a macro (i.e., keyboard action sequence)."));
actionCollection()->setDefaultShortcut(m_recordAction, QKeySequence(QStringLiteral("Ctrl+Shift+K"), QKeySequence::PortableText));
connect(m_recordAction, &QAction::triggered, plugin, [this] {
slotRecord();
});
menu->addAction(m_recordAction);
// create cancel action
m_cancelAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_cancel"));
m_cancelAction->setText(i18n("&Cancel Macro Recording"));
m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
m_cancelAction->setToolTip(i18n("Cancel ongoing recording (and keep the previous macro as the current one)."));
actionCollection()->setDefaultShortcut(m_cancelAction, QKeySequence(QStringLiteral("Ctrl+Alt+Shift+K"), QKeySequence::PortableText));
m_cancelAction->setEnabled(false);
connect(m_cancelAction, &QAction::triggered, plugin, [this] {
slotCancel();
});
menu->addAction(m_cancelAction);
// create play action
m_playAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_play"));
m_playAction->setText(i18n("&Play Macro"));
m_playAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
m_playAction->setToolTip(i18n("Play current macro (i.e., execute the last recorded keyboard action sequence)."));
actionCollection()->setDefaultShortcut(m_playAction, QKeySequence(QStringLiteral("Ctrl+Alt+K"), QKeySequence::PortableText));
m_playAction->setEnabled(false);
connect(m_playAction, &QAction::triggered, plugin, [this] {
slotPlay();
});
menu->addAction(m_playAction);
// create save action
m_saveAction = actionCollection()->addAction(QStringLiteral("keyboardmacros_save"));
m_saveAction->setText(i18n("&Save Current Macro"));
m_saveAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playlist-append")));
m_saveAction->setToolTip(i18n("Give a name to the current macro and persistently save it."));
actionCollection()->setDefaultShortcut(m_saveAction, QKeySequence(QStringLiteral("Alt+Shift+K"), QKeySequence::PortableText));
m_saveAction->setEnabled(false);
connect(m_saveAction, &QAction::triggered, plugin, [this] {
slotSave();
});
menu->addAction(m_saveAction);
// add separator
menu->addSeparator();
// create load named menu
m_loadMenu = new KActionMenu(i18n("&Load Named Macro..."), menu);
m_loadMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
actionCollection()->addAction(QStringLiteral("keyboardmacros_named_load"), m_loadMenu);
m_loadMenu->setToolTip(i18n("Load a named macro as the current one."));
m_loadMenu->setEnabled(!plugin->m_namedMacros.isEmpty());
menu->addAction(m_loadMenu);
// create play named menu
m_playMenu = new KActionMenu(i18n("&Play Named Macro..."), menu);
m_playMenu->setIcon(QIcon::fromTheme(QStringLiteral("auto-type")));
actionCollection()->addAction(QStringLiteral("keyboardmacros_named_play"), m_playMenu);
m_playMenu->setToolTip(i18n("Play a named macro without loading it."));
m_playMenu->setEnabled(!plugin->m_namedMacros.isEmpty());
menu->addAction(m_playMenu);
// create wipe named menu
m_wipeMenu = new KActionMenu(i18n("&Wipe Named Macro..."), menu);
m_wipeMenu->setIcon(QIcon::fromTheme(QStringLiteral("delete")));
actionCollection()->addAction(QStringLiteral("keyboardmacros_named_wipe"), m_wipeMenu);
m_wipeMenu->setToolTip(i18n("Wipe a named macro."));
m_wipeMenu->setEnabled(!plugin->m_namedMacros.isEmpty());
menu->addAction(m_wipeMenu);
// add named macros to our menus
for (const auto &[name, macro] : plugin->m_namedMacros.toStdMap()) {
addNamedMacro(name, macro.toString());
}
// update current state if necessary
if (plugin->m_recording) {
recordingOn();
}
if (!plugin->m_macro.isEmpty()) {
macroLoaded(true);
}
// add Keyboard Macros actions to the GUI
mainwindow->guiFactory()->addClient(this);
}
KeyboardMacrosPluginView::~KeyboardMacrosPluginView()
{
// remove Keyboard Macros actions from the GUI
m_mainWindow->guiFactory()->removeClient(this);
// deregister this view from the plugin
m_plugin->m_pluginViews.removeOne(this);
}
QKeySequence KeyboardMacrosPluginView::recordActionShortcut() const
{
return m_recordAction->shortcut();
}
QKeySequence KeyboardMacrosPluginView::playActionShortcut() const
{
return m_playAction->shortcut();
}
void KeyboardMacrosPluginView::recordingOn()
{
m_recordAction->setText(i18n("End Macro &Recording"));
m_recordAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop")));
m_cancelAction->setEnabled(true);
m_playAction->setEnabled(true);
}
void KeyboardMacrosPluginView::recordingOff()
{
m_recordAction->setText(i18n("&Record Macro..."));
m_recordAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
m_cancelAction->setEnabled(false);
}
void KeyboardMacrosPluginView::macroLoaded(bool enable)
{
m_playAction->setEnabled(enable);
m_saveAction->setEnabled(enable);
}
void KeyboardMacrosPluginView::addNamedMacro(const QString &name, const QString &description)
{
QAction *action;
QString label = KStringHandler::rsqueeze(name + QStringLiteral(": ") + description, 50)
// avoid unwanted accelerators
.replace(QRegularExpression(QStringLiteral("&(?!&)")), QStringLiteral("&&"));
// add load action
action = new QAction(QStringLiteral("Load ") + label);
action->setToolTip(i18n("Load the '%1' macro as the current one.", name));
action->setEnabled(true);
connect(action, &QAction::triggered, m_plugin, [this, name] {
slotLoadNamed(name);
});
m_loadMenu->addAction(action);
// remember load action pointer
m_namedMacrosLoadActions.insert(name, action);
// update load menu state
m_loadMenu->setEnabled(true);
// add play action
action = new QAction(QStringLiteral("Play ") + label);
action->setToolTip(i18n("Play the '%1' macro without loading it.", name));
action->setEnabled(true);
connect(action, &QAction::triggered, m_plugin, [this, name] {
slotPlayNamed(name);
});
m_playMenu->addAction(action);
// add the play action to the collection (a user may want to set a shortcut for a macro they use very often)
actionCollection()->addAction(QStringLiteral("keyboardmacros_named_play_") + name, action);
// remember play action pointer
m_namedMacrosPlayActions.insert(name, action);
// update play menu state
m_playMenu->setEnabled(true);
// add wipe action
action = new QAction(QStringLiteral("Wipe ") + label);
action->setToolTip(i18n("Wipe the '%1' macro.", name));
action->setEnabled(true);
connect(action, &QAction::triggered, m_plugin, [this, name] {
slotWipeNamed(name);
});
m_wipeMenu->addAction(action);
// remember wipe action pointer
m_namedMacrosWipeActions.insert(name, action);
// update wipe menu state
m_wipeMenu->setEnabled(true);
}
void KeyboardMacrosPluginView::removeNamedMacro(const QString &name)
{
QAction *action;
// remove load action
action = m_namedMacrosLoadActions.value(name);
m_loadMenu->removeAction(action);
actionCollection()->removeAction(action);
// forget load action pointer
m_namedMacrosLoadActions.remove(name);
// update load menu state
m_loadMenu->setEnabled(!m_namedMacrosLoadActions.isEmpty());
// remove play action
action = m_namedMacrosPlayActions.value(name);
m_playMenu->removeAction(action);
actionCollection()->removeAction(action);
// forget play action pointer
m_namedMacrosPlayActions.remove(name);
// update play menu state
m_playMenu->setEnabled(!m_namedMacrosPlayActions.isEmpty());
// remove wipe action
action = m_namedMacrosWipeActions.value(name);
m_wipeMenu->removeAction(action);
actionCollection()->removeAction(action);
// forget wipe action pointer
m_namedMacrosWipeActions.remove(name);
// update wipe menu state
m_wipeMenu->setEnabled(!m_namedMacrosWipeActions.isEmpty());
}
// BEGIN Action slots
void KeyboardMacrosPluginView::slotRecord()
{
if (m_plugin->m_recording) {
m_plugin->stop(true);
} else {
m_plugin->record();
}
}
void KeyboardMacrosPluginView::slotPlay()
{
if (m_plugin->m_recording) {
m_plugin->stop(true);
}
m_plugin->play();
}
void KeyboardMacrosPluginView::slotCancel()
{
if (!m_plugin->m_recording) {
return;
}
m_plugin->cancel();
}
void KeyboardMacrosPluginView::slotSave()
{
if (m_plugin->m_recording) {
return;
}
bool ok;
QString name =
QInputDialog::getText(m_mainWindow->window(), i18n("Keyboard Macros"), i18n("Save current macro as?"), QLineEdit::Normal, QStringLiteral(""), &ok);
if (!ok || name.isEmpty()) {
return;
}
m_plugin->save(name);
}
void KeyboardMacrosPluginView::slotLoadNamed(const QString &name)
{
if (m_plugin->m_recording) {
return;
}
if (name.isEmpty()) {
return;
}
m_plugin->load(name);
}
void KeyboardMacrosPluginView::slotPlayNamed(const QString &name)
{
if (m_plugin->m_recording) {
return;
}
if (name.isEmpty()) {
return;
}
m_plugin->play(name);
}
void KeyboardMacrosPluginView::slotWipeNamed(const QString &name)
{
if (m_plugin->m_recording) {
return;
}
if (QMessageBox::question(m_mainWindow->window(), i18n("Keyboard Macros"), i18n("Wipe the '%1' macro?", name)) != QMessageBox::Yes) {
return;
}
m_plugin->wipe(name);
}
// END
// END
// BEGIN Plugin commands to manage named keyboard macros
KeyboardMacrosPluginCommands::KeyboardMacrosPluginCommands(KeyboardMacrosPlugin *plugin)
: KTextEditor::Command(QStringList() << QStringLiteral("kmsave") << QStringLiteral("kmload") << QStringLiteral("kmplay") << QStringLiteral("kmwipe"),
plugin)
, m_plugin(plugin)
{
}
bool KeyboardMacrosPluginCommands::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &)
{
const QStringList &actionAndName = cmd.split(QRegularExpression(QStringLiteral("\\s+")));
const QString &action = actionAndName[0];
// kmplay can take either zero or one argument, all other commands require exactly one
if (actionAndName.length() > 2 || (action != QStringLiteral("kmplay") && actionAndName.length() != 2)) {
msg = i18n("Usage: %1 <name>.", action);
return false;
}
if (action == QStringLiteral("kmplay")) {
// set focus on the view otherwise the macro is executed in the command line
view->setFocus();
if (actionAndName.length() == 1) {
// no argument: play the current macro
m_plugin->play();
return true;
} else {
// otherwise play the given macro
const QString &name = actionAndName[1];
if (!m_plugin->play(name)) {
msg = i18n("No keyboard macro named '%1' found.", name);
return false;
}
return true;
}
}
const QString &name = actionAndName[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("kmwipe")) {
if (!m_plugin->wipe(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)
{
QString macros;
if (!m_plugin->m_namedMacros.keys().isEmpty()) {
macros = QStringLiteral("<p><b>Named macros:</b> ") + QStringList(m_plugin->m_namedMacros.keys()).join(QStringLiteral(", ")) + QStringLiteral(".</p>");
}
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>%1</qt>", macros);
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>%1</qt>",
macros);
return true;
} else if (cmd == QStringLiteral("kmplay")) {
msg = i18n("<qt><p>Usage: <code>kmplay &lt;name&gt;</code></p><p>Play saved keyboard macro <code>&lt;name&gt;</code> without loading it.</p>%1</qt>",
macros);
return true;
} else if (cmd == QStringLiteral("kmwipe")) {
msg = i18n("<qt><p>Usage: <code>kmwipe &lt;name&gt;</code></p><p>Wipe saved keyboard macro <code>&lt;name&gt;</code>.</p>%1</qt>", macros);
return true;
}
return false;
}
// END
// required for KeyboardMacrosPluginFactory vtable
#include "keyboardmacrosplugin.moc"
......@@ -16,10 +16,7 @@
#include <QVariant>
#include <QVariantMap>
#include <KActionMenu>
#include <KTextEditor/Application>
#include <KTextEditor/Command>
#include <KTextEditor/MainWindow>
#include <KTextEditor/Message>
#include <KTextEditor/Plugin>
......@@ -53,8 +50,8 @@ class KeyboardMacrosPlugin : public KTextEditor::Plugin
QMap<QString, Macro> m_namedMacros;
QSet<QString> m_wipedMacros;
public:
// Plugin creation and destruction
public:
explicit KeyboardMacrosPlugin(QObject *parent = nullptr, const QList<QVariant> & = QList<QVariant>());
~KeyboardMacrosPlugin() override;
QObject *createView(KTextEditor::MainWindow *mainWindow) override;
......@@ -88,66 +85,4 @@ private:
bool wipe(const QString &name);
};
/**
* Plugin view to add keyboard macros actions to the GUI
*/
class KeyboardMacrosPluginView : public QObject, public KXMLGUIClient
{
Q_OBJECT
KeyboardMacrosPlugin *m_plugin;
KTextEditor::MainWindow *m_mainWindow;
QPointer<QAction> m_recordAction;
QPointer<QAction> m_cancelAction;
QPointer<QAction> m_playAction;
QPointer<QAction> m_saveAction;
QPointer<KActionMenu> m_loadMenu;
QMap<QString, QPointer<QAction>> m_namedMacrosLoadActions;
QPointer<KActionMenu> m_playMenu;
QMap<QString, QPointer<QAction>> m_namedMacrosPlayActions;
QPointer<KActionMenu> m_wipeMenu;
QMap<QString, QPointer<QAction>> m_namedMacrosWipeActions;
public:
explicit KeyboardMacrosPluginView(KeyboardMacrosPlugin *plugin, KTextEditor::MainWindow *mainwindow);
~KeyboardMacrosPluginView() override;
// shortcut getter
QKeySequence recordActionShortcut() const;
QKeySequence playActionShortcut() const;
// GUI update helpers
void recordingOn();
void recordingOff();
void macroLoaded(bool enable);
void addNamedMacro(const QString &name, const QString &description);
void removeNamedMacro(const QString &name);
// Action slots
public Q_SLOTS:
void slotRecord();
void slotCancel();
void slotPlay();
void slotSave();
void slotLoadNamed(const QString &name = QString());
void slotPlayNamed(const QString &name = QString());
void slotWipeNamed(const QString &name = QString());
};
/**
* Plugin commands to manage named keyboard macros
*/
class KeyboardMacrosPluginCommands : public KTextEditor::Command
{
Q_OBJECT
public:
explicit KeyboardMacrosPluginCommands(KeyboardMacrosPlugin *plugin);
bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &) 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: LGPL-2.0-or-later
*/
#include <QDebug>
#include <QKeyEvent>
#include <QObject>
#include <QString>
#include <QStringList>
#include <KLocalizedString>
#include <KTextEditor/Command>
#include <KTextEditor/Range>
#include <KTextEditor/View>
#include "keyboardmacrosplugin.h"
#include "keyboardmacrosplugincommands.h"
KeyboardMacrosPluginCommands::KeyboardMacrosPluginCommands(KeyboardMacrosPlugin *plugin)
: KTextEditor::Command(QStringList() << QStringLiteral("kmsave") << QStringLiteral("kmload") << QStringLiteral("kmplay") << QStringLiteral("kmwipe"),
plugin)
, m_plugin(plugin)
{
}
bool KeyboardMacrosPluginCommands::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &)
{
const QStringList &actionAndName = cmd.split(QRegularExpression(QStringLiteral("\\s+")));
const QString &action = actionAndName[0];
// kmplay can take either zero or one argument, all other commands require exactly one
if (actionAndName.length() > 2 || (action != QStringLiteral("kmplay") && actionAndName.length() != 2)) {
msg = i18n("Usage: %1 <name>.", action);
return false;
}
if (action == QStringLiteral("kmplay")) {
// set focus on the view otherwise the macro is executed in the command line
view->setFocus();
if (actionAndName.length() == 1) {
// no argument: play the current macro
m_plugin->play();
return true;
} else {
// otherwise play the given macro
const QString &name = actionAndName[1];
if (!m_plugin->play(name)) {