diff options
author | Simon Rettberg | 2018-09-06 11:32:54 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-09-06 11:32:54 +0200 |
commit | cb93788148785758be72fcb2afe2039e7a882c53 (patch) | |
tree | c2095f4ac5995d53e62bd9e247345a1b60c3bf5a /src/client/addons/addons.cpp | |
parent | [client] Fix memory leak (diff) | |
download | pvs2-cb93788148785758be72fcb2afe2039e7a882c53.tar.gz pvs2-cb93788148785758be72fcb2afe2039e7a882c53.tar.xz pvs2-cb93788148785758be72fcb2afe2039e7a882c53.zip |
[client] New addon system for buttons and menu
Instead of integrating workspace switching and screen locking
directly into PVS2, introduce an addon system to insert new
functionality into the toolbar, as buttons or menu entries.
Diffstat (limited to 'src/client/addons/addons.cpp')
-rw-r--r-- | src/client/addons/addons.cpp | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/client/addons/addons.cpp b/src/client/addons/addons.cpp new file mode 100644 index 0000000..9f4a99f --- /dev/null +++ b/src/client/addons/addons.cpp @@ -0,0 +1,235 @@ +#include "addons.h" + +#include <QDir> +#include <QSettings> +#include <QPushButton> +#include <QAction> +#include <QProcess> +#include <QtDebug> +#include <QRegularExpression> + +#define MKSTR(x) static const QString s_ ## x(#x) + +MKSTR(EVENT); +MKSTR(ADDRESS); +MKSTR(ISLOCAL); +MKSTR(CHECKED); +MKSTR(ENABLED); +MKSTR(VISIBLE); +MKSTR(connected); +MKSTR(disconnected); +MKSTR(init); +MKSTR(clicked); +MKSTR(true); +MKSTR(false); + +static const QSize ICON_SIZE(20, 20); + +QList<Addon*> AddonManager::_addons; + +class Addon +{ +public: + Addon() : button(nullptr), menu(nullptr), wantConnectInfo(false) {} + QPushButton *button; + QAction *menu; + QProcess process; + bool wantConnectInfo; + bool wantInit; + bool runAsync; + QList<QPair<QString, QString>> envir; +}; + +static inline bool toBool(const QString &string) +{ + return string.toLower() == s_true; +} + +static void executeAddon(Addon *addon); + +static void setAddonVisible(Addon *addon, bool visible); + +static void handleAddonOutput(Addon *addon, QRegularExpressionMatchIterator &matches); + +void AddonManager::loadFromPath(const QString &path, QList<QPushButton*> &buttons, QList<QAction*> &menuEntries) +{ + QDir configDir(path); + QFileInfoList fileInfoList = configDir.entryInfoList(QDir::Files, QDir::Name); + QRegularExpression paramRegex("^([A-Z]+)=(.*)$", QRegularExpression::MultilineOption); + + for (QFileInfo fileInfo : fileInfoList) { + QString filePath = fileInfo.absoluteFilePath(); + QSettings setting(filePath, QSettings::IniFormat); + QString caption = setting.value("caption").toString(); // TODO: i18n + QString exec = setting.value("exec").toString(); + QString type = setting.value("type").toString(); + QString tooltip = setting.value("tooltip").toString(); + QIcon icon(setting.value("icon").toString()); + bool checkable = setting.value("checkable").toBool(); + if (exec.isEmpty() || (caption.isEmpty() && icon.isNull())) { + qDebug() << "Ignoring" << filePath << "caption+icon or exec empty"; + continue; + } + if (!QFileInfo(exec).isExecutable() || !QFileInfo(exec).isFile()) { + qDebug() << "Ignoring" << filePath << "since target" << exec << "doesn't exist or isn't an executable file"; + continue; + } + + // Alloc addon + Addon *addon = new Addon(); + // Toggle/click callback + auto toggleFun = [=](bool value) { + addon->envir.append(qMakePair(s_EVENT, s_clicked)); + addon->envir.append(qMakePair(s_CHECKED, (value ? s_true : s_false))); + executeAddon(addon); + }; + if (type == "menu") { + addon->menu = new QAction(caption); + if (!icon.isNull()) { + addon->menu->setIcon(icon); + } + addon->menu->setCheckable(checkable); + addon->menu->setToolTip(tooltip); + menuEntries.append(addon->menu); + addon->menu->connect(addon->menu, &QAction::triggered, toggleFun); + } else if (type == "button") { + addon->button = new QPushButton(caption); + if (!icon.isNull()) { + addon->button->setIcon(icon); + addon->button->setIconSize(ICON_SIZE); + } + addon->button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); + addon->button->setCheckable(checkable); + addon->button->setToolTip(tooltip); + buttons.append(addon->button); + addon->button->connect(addon->button, &QPushButton::clicked, toggleFun); + } else { + qDebug() << "Ignoring unknown addon type" << type; + delete addon; + continue; + } + _addons.append(addon); + + addon->wantConnectInfo = setting.value("connection-events").toBool(); + addon->wantInit = setting.value("init").toBool(); + addon->runAsync = setting.value("async").toBool(); + // Setup process + addon->process.setProgram(exec); + // Stdin for status updates + addon->process.connect(&addon->process, &QProcess::readyReadStandardOutput, [=]() { + auto lines = addon->process.readAllStandardOutput(); + auto matches = paramRegex.globalMatch(lines); + handleAddonOutput(addon, matches); + }); + // Stderr just for debugging + addon->process.connect(&addon->process, &QProcess::readyReadStandardError, [=]() { + qDebug() << exec << "stderr:" << QString::fromLocal8Bit(addon->process.readAllStandardError()); + }); + } +} + +void AddonManager::initControls() +{ + // Call all init functions + for (auto addon : _addons) { + if (addon->wantInit) { + addon->envir.append(qMakePair(s_EVENT, s_init)); + executeAddon(addon); + } else { + setAddonVisible(addon, true); + } + } +} + +void AddonManager::connectEvent(bool isLocal, const QString &address) +{ + for (auto addon : _addons) { + if (!addon->wantConnectInfo) + continue; + addon->envir.append(qMakePair(s_EVENT, s_connected)); + addon->envir.append(qMakePair(s_ADDRESS, address)); + addon->envir.append(qMakePair(s_ISLOCAL, isLocal ? s_true : s_false)); + executeAddon(addon); + } +} + +void AddonManager::disconnectEvent() +{ + for (auto addon : _addons) { + if (!addon->wantConnectInfo) + continue; + addon->envir.append(qMakePair(s_EVENT, s_disconnected)); + executeAddon(addon); + } +} + +static void executeAddon(Addon *addon) +{ + // Set up environment + auto env = QProcessEnvironment::systemEnvironment(); + for (auto e : addon->envir) { + env.insert(e.first, e.second); + } + addon->envir.clear(); + if (!addon->runAsync) { + // Kill remains + if (addon->process.state() != QProcess::NotRunning) { + addon->process.waitForFinished(500); + } + if (addon->process.state() != QProcess::NotRunning) { + addon->process.close(); + } + } + addon->process.setProcessEnvironment(env); + // Run + if (addon->runAsync) { + addon->process.startDetached(addon->process.program(), QStringList()); + } else { + addon->process.start(); + addon->process.closeWriteChannel(); + } +} + +static void setAddonVisible(Addon *addon, bool newValue) +{ + if (addon->button != nullptr) { + QWidget *p = addon->button->parentWidget(); + bool wasVisible = p != nullptr && addon->button->isVisibleTo(p); + qDebug() << "Visibility" << wasVisible << "->" << newValue; + addon->button->setVisible(newValue); + if (p != nullptr && wasVisible != newValue) { + // Visibility changed -- adjust size of toolbar + int size = (addon->button->width() + 2) * (newValue ? 1 : -1); + qDebug() << "Adding" << size; + p->setFixedWidth(p->width() + size); + } + } else { + addon->menu->setVisible(newValue); + } +} + +static void handleAddonOutput(Addon* addon, QRegularExpressionMatchIterator &matches) +{ + while (matches.hasNext()) { + auto m = matches.next(); + qDebug() << "Match:" << m.captured(0); + auto key = m.captured(1); + auto val = m.captured(2); + bool newValue = toBool(val); + if (key == s_VISIBLE) { + setAddonVisible(addon, newValue); + } else if (key == s_CHECKED) { + if (addon->button != nullptr) { + addon->button->setChecked(newValue); + } else { + addon->menu->setChecked(newValue); + } + } else if (key == s_ENABLED) { + if (addon->button != nullptr) { + addon->button->setEnabled(newValue); + } else { + addon->menu->setEnabled(newValue); + } + } + } +} |