diff --git a/CMakeLists.txt b/CMakeLists.txt index 774d28d1..7a57aa8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ qt_add_executable(qtshell src/cpp/rootwrapper.cpp src/cpp/qmlglobal.cpp src/cpp/qmlscreen.cpp + src/cpp/watcher.cpp ) qt_add_qml_module(qtshell URI QtShell) diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp index f3dcfd50..1368c32c 100644 --- a/src/cpp/main.cpp +++ b/src/cpp/main.cpp @@ -11,7 +11,7 @@ #include "rootwrapper.hpp" #ifdef CONF_LAYERSHELL -# include +#include #endif int main(int argc, char** argv) { diff --git a/src/cpp/qmlglobal.hpp b/src/cpp/qmlglobal.hpp index e59f179a..5f11e0ec 100644 --- a/src/cpp/qmlglobal.hpp +++ b/src/cpp/qmlglobal.hpp @@ -13,7 +13,7 @@ class QtShellGlobal: public QObject { Q_OBJECT; Q_PROPERTY(QQmlListProperty screens READ screens NOTIFY screensChanged); QML_SINGLETON; - QML_ELEMENT; + QML_NAMED_ELEMENT(QtShell); public: QtShellGlobal(QObject* parent = nullptr); diff --git a/src/cpp/rootwrapper.cpp b/src/cpp/rootwrapper.cpp index 87b0d7a4..d0b5a1a5 100644 --- a/src/cpp/rootwrapper.cpp +++ b/src/cpp/rootwrapper.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -10,23 +11,26 @@ #include "scavenge.hpp" #include "shell.hpp" +#include "watcher.hpp" -RootWrapper::RootWrapper(QUrl rootUrl): - QObject(nullptr), rootUrl(std::move(rootUrl)), engine(this) { +RootWrapper::RootWrapper(QString rootPath): + QObject(nullptr), rootPath(std::move(rootPath)), engine(this) { this->reloadGraph(true); - if (this->activeRoot == nullptr) { + if (this->root == nullptr) { qCritical() << "could not create scene graph, exiting"; exit(-1); // NOLINT } } +QObject* RootWrapper::scavengeTargetFor(QObject* /* child */) { return this->root; } + void RootWrapper::reloadGraph(bool hard) { - if (this->activeRoot != nullptr) { + if (this->root != nullptr) { this->engine.clearComponentCache(); } - auto component = QQmlComponent(&this->engine, this->rootUrl); + auto component = QQmlComponent(&this->engine, QUrl::fromLocalFile(this->rootPath)); SCAVENGE_PARENT = hard ? nullptr : this; auto* obj = component.beginCreate(this->engine.rootContext()); @@ -38,7 +42,7 @@ void RootWrapper::reloadGraph(bool hard) { return; } - auto* newRoot = qobject_cast(obj); + auto* newRoot = qobject_cast(obj); if (newRoot == nullptr) { qWarning() << "root component was not a QtShell"; delete obj; @@ -47,26 +51,34 @@ void RootWrapper::reloadGraph(bool hard) { component.completeCreate(); - if (this->activeRoot != nullptr) { - this->activeRoot->deleteLater(); - this->activeRoot = nullptr; + if (this->root != nullptr) { + this->root->deleteLater(); + this->root = nullptr; } - this->activeRoot = newRoot; + this->root = newRoot; + this->onConfigChanged(); } -void RootWrapper::changeRoot(QtShell* newRoot) { - if (this->activeRoot != nullptr) { - QObject::disconnect(this->destroyConnection); - this->activeRoot->deleteLater(); - } +void RootWrapper::onConfigChanged() { + auto config = this->root->config(); - if (newRoot != nullptr) { - this->activeRoot = newRoot; - QObject::connect(this->activeRoot, &QtShell::destroyed, this, &RootWrapper::destroy); + if (config.mWatchFiles && this->configWatcher == nullptr) { + this->configWatcher = new FiletreeWatcher(); + this->configWatcher->addPath(QFileInfo(this->rootPath).dir().path()); + + QObject::connect(this->root, &ShellRoot::configChanged, this, &RootWrapper::onConfigChanged); + + QObject::connect( + this->configWatcher, + &FiletreeWatcher::fileChanged, + this, + &RootWrapper::onWatchedFilesChanged + ); + } else if (!config.mWatchFiles && this->configWatcher != nullptr) { + this->configWatcher->deleteLater(); + this->configWatcher = nullptr; } } -QObject* RootWrapper::scavengeTargetFor(QObject* /* child */) { return this->activeRoot; } - -void RootWrapper::destroy() { this->deleteLater(); } +void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); } diff --git a/src/cpp/rootwrapper.hpp b/src/cpp/rootwrapper.hpp index 73b6bed2..2f463042 100644 --- a/src/cpp/rootwrapper.hpp +++ b/src/cpp/rootwrapper.hpp @@ -1,31 +1,31 @@ #pragma once #include -#include #include #include #include #include "scavenge.hpp" #include "shell.hpp" +#include "watcher.hpp" class RootWrapper: public QObject, virtual public Scavengeable { Q_OBJECT; public: - explicit RootWrapper(QUrl rootUrl); - - void reloadGraph(bool hard); - void changeRoot(QtShell* newRoot); + explicit RootWrapper(QString rootPath); QObject* scavengeTargetFor(QObject* child) override; + void reloadGraph(bool hard); + private slots: - void destroy(); + void onConfigChanged(); + void onWatchedFilesChanged(); private: - QUrl rootUrl; + QString rootPath; QQmlEngine engine; - QtShell* activeRoot = nullptr; - QMetaObject::Connection destroyConnection; + ShellRoot* root = nullptr; + FiletreeWatcher* configWatcher = nullptr; }; diff --git a/src/cpp/shell.cpp b/src/cpp/shell.cpp index fa34193c..5b4f2313 100644 --- a/src/cpp/shell.cpp +++ b/src/cpp/shell.cpp @@ -3,16 +3,17 @@ #include #include +#include -void QtShell::earlyInit(QObject* old) { - auto* oldshell = qobject_cast(old); +void ShellRoot::earlyInit(QObject* old) { + auto* oldshell = qobject_cast(old); if (oldshell != nullptr) { this->scavengeableChildren = std::move(oldshell->children); } } -QObject* QtShell::scavengeTargetFor(QObject* /* child */) { +QObject* ShellRoot::scavengeTargetFor(QObject* /* child */) { if (this->scavengeableChildren.length() > this->children.length()) { return this->scavengeableChildren[this->children.length()]; } @@ -20,11 +21,19 @@ QObject* QtShell::scavengeTargetFor(QObject* /* child */) { return nullptr; } -QQmlListProperty QtShell::components() { +void ShellRoot::setConfig(ShellConfig config) { + this->mConfig = config; + + emit this->configChanged(); +} + +ShellConfig ShellRoot::config() const { return this->mConfig; } + +QQmlListProperty ShellRoot::components() { return QQmlListProperty( this, nullptr, - &QtShell::appendComponent, + &ShellRoot::appendComponent, nullptr, nullptr, nullptr, @@ -33,8 +42,8 @@ QQmlListProperty QtShell::components() { ); } -void QtShell::appendComponent(QQmlListProperty* list, QObject* component) { - auto* shell = static_cast(list->object); // NOLINT +void ShellRoot::appendComponent(QQmlListProperty* list, QObject* component) { + auto* shell = static_cast(list->object); // NOLINT component->setParent(shell); shell->children.append(component); } diff --git a/src/cpp/shell.hpp b/src/cpp/shell.hpp index 4cdd360c..0cfd794f 100644 --- a/src/cpp/shell.hpp +++ b/src/cpp/shell.hpp @@ -9,24 +9,40 @@ #include "scavenge.hpp" -class QtShell: public Scavenger, virtual public Scavengeable { +class ShellConfig { + Q_GADGET; + Q_PROPERTY(bool watchFiles MEMBER mWatchFiles); + +public: + bool mWatchFiles = true; +}; + +class ShellRoot: public Scavenger, virtual public Scavengeable { Q_OBJECT; + Q_PROPERTY(ShellConfig config READ config WRITE setConfig); Q_PROPERTY(QQmlListProperty components READ components FINAL); Q_CLASSINFO("DefaultProperty", "components"); QML_ELEMENT; public: - explicit QtShell(QObject* parent = nullptr): Scavenger(parent) {} + explicit ShellRoot(QObject* parent = nullptr): Scavenger(parent) {} void earlyInit(QObject* old) override; QObject* scavengeTargetFor(QObject* child) override; + void setConfig(ShellConfig config); + [[nodiscard]] ShellConfig config() const; + QQmlListProperty components(); +signals: + void configChanged(); + private: static void appendComponent(QQmlListProperty* list, QObject* component); -public: + ShellConfig mConfig; + // track only the children assigned to `components` in order QList children; QList scavengeableChildren; diff --git a/src/cpp/watcher.cpp b/src/cpp/watcher.cpp new file mode 100644 index 00000000..6b06d584 --- /dev/null +++ b/src/cpp/watcher.cpp @@ -0,0 +1,38 @@ +#include "watcher.hpp" + +#include +#include +#include +#include +#include + +FiletreeWatcher::FiletreeWatcher(QObject* parent): QObject(parent) { + QObject::connect( + &this->watcher, + &QFileSystemWatcher::fileChanged, + this, + &FiletreeWatcher::onFileChanged + ); + + QObject::connect( + &this->watcher, + &QFileSystemWatcher::directoryChanged, + this, + &FiletreeWatcher::onDirectoryChanged + ); +} +void FiletreeWatcher::addPath(const QString& path) { + this->watcher.addPath(path); + + if (QFileInfo(path).isDir()) { + auto dir = QDir(path); + + for (auto& entry: dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot)) { + this->addPath(dir.filePath(entry)); + } + } +} + +void FiletreeWatcher::onDirectoryChanged(const QString& path) { this->addPath(path); } + +void FiletreeWatcher::onFileChanged(const QString& path) { emit this->fileChanged(path); } diff --git a/src/cpp/watcher.hpp b/src/cpp/watcher.hpp new file mode 100644 index 00000000..a729f03c --- /dev/null +++ b/src/cpp/watcher.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +class FiletreeWatcher: public QObject { + Q_OBJECT; + +public: + explicit FiletreeWatcher(QObject* parent = nullptr); + + void addPath(const QString& path); + +signals: + void fileChanged(const QString& path); + +private slots: + void onDirectoryChanged(const QString& path); + void onFileChanged(const QString& path); + +private: + QFileSystemWatcher watcher; +};