feat: add filesystem watcher and rename QtShell object

This commit is contained in:
outfoxxed 2024-02-04 23:00:59 -08:00
parent cab5ffc65e
commit c0d6e63f6c
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
9 changed files with 142 additions and 42 deletions

View file

@ -37,6 +37,7 @@ qt_add_executable(qtshell
src/cpp/rootwrapper.cpp src/cpp/rootwrapper.cpp
src/cpp/qmlglobal.cpp src/cpp/qmlglobal.cpp
src/cpp/qmlscreen.cpp src/cpp/qmlscreen.cpp
src/cpp/watcher.cpp
) )
qt_add_qml_module(qtshell URI QtShell) qt_add_qml_module(qtshell URI QtShell)

View file

@ -13,7 +13,7 @@ class QtShellGlobal: public QObject {
Q_OBJECT; Q_OBJECT;
Q_PROPERTY(QQmlListProperty<QtShellScreenInfo> screens READ screens NOTIFY screensChanged); Q_PROPERTY(QQmlListProperty<QtShellScreenInfo> screens READ screens NOTIFY screensChanged);
QML_SINGLETON; QML_SINGLETON;
QML_ELEMENT; QML_NAMED_ELEMENT(QtShell);
public: public:
QtShellGlobal(QObject* parent = nullptr); QtShellGlobal(QObject* parent = nullptr);

View file

@ -2,6 +2,7 @@
#include <cstdlib> #include <cstdlib>
#include <utility> #include <utility>
#include <qfileinfo.h>
#include <qlogging.h> #include <qlogging.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlcomponent.h> #include <qqmlcomponent.h>
@ -10,23 +11,26 @@
#include "scavenge.hpp" #include "scavenge.hpp"
#include "shell.hpp" #include "shell.hpp"
#include "watcher.hpp"
RootWrapper::RootWrapper(QUrl rootUrl): RootWrapper::RootWrapper(QString rootPath):
QObject(nullptr), rootUrl(std::move(rootUrl)), engine(this) { QObject(nullptr), rootPath(std::move(rootPath)), engine(this) {
this->reloadGraph(true); this->reloadGraph(true);
if (this->activeRoot == nullptr) { if (this->root == nullptr) {
qCritical() << "could not create scene graph, exiting"; qCritical() << "could not create scene graph, exiting";
exit(-1); // NOLINT exit(-1); // NOLINT
} }
} }
QObject* RootWrapper::scavengeTargetFor(QObject* /* child */) { return this->root; }
void RootWrapper::reloadGraph(bool hard) { void RootWrapper::reloadGraph(bool hard) {
if (this->activeRoot != nullptr) { if (this->root != nullptr) {
this->engine.clearComponentCache(); this->engine.clearComponentCache();
} }
auto component = QQmlComponent(&this->engine, this->rootUrl); auto component = QQmlComponent(&this->engine, QUrl::fromLocalFile(this->rootPath));
SCAVENGE_PARENT = hard ? nullptr : this; SCAVENGE_PARENT = hard ? nullptr : this;
auto* obj = component.beginCreate(this->engine.rootContext()); auto* obj = component.beginCreate(this->engine.rootContext());
@ -38,7 +42,7 @@ void RootWrapper::reloadGraph(bool hard) {
return; return;
} }
auto* newRoot = qobject_cast<QtShell*>(obj); auto* newRoot = qobject_cast<ShellRoot*>(obj);
if (newRoot == nullptr) { if (newRoot == nullptr) {
qWarning() << "root component was not a QtShell"; qWarning() << "root component was not a QtShell";
delete obj; delete obj;
@ -47,26 +51,34 @@ void RootWrapper::reloadGraph(bool hard) {
component.completeCreate(); component.completeCreate();
if (this->activeRoot != nullptr) { if (this->root != nullptr) {
this->activeRoot->deleteLater(); this->root->deleteLater();
this->activeRoot = nullptr; this->root = nullptr;
} }
this->activeRoot = newRoot; this->root = newRoot;
this->onConfigChanged();
} }
void RootWrapper::changeRoot(QtShell* newRoot) { void RootWrapper::onConfigChanged() {
if (this->activeRoot != nullptr) { auto config = this->root->config();
QObject::disconnect(this->destroyConnection);
this->activeRoot->deleteLater();
}
if (newRoot != nullptr) { if (config.mWatchFiles && this->configWatcher == nullptr) {
this->activeRoot = newRoot; this->configWatcher = new FiletreeWatcher();
QObject::connect(this->activeRoot, &QtShell::destroyed, this, &RootWrapper::destroy); 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::onWatchedFilesChanged() { this->reloadGraph(false); }
void RootWrapper::destroy() { this->deleteLater(); }

View file

@ -1,31 +1,31 @@
#pragma once #pragma once
#include <qobject.h> #include <qobject.h>
#include <qobjectdefs.h>
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qurl.h> #include <qurl.h>
#include "scavenge.hpp" #include "scavenge.hpp"
#include "shell.hpp" #include "shell.hpp"
#include "watcher.hpp"
class RootWrapper: public QObject, virtual public Scavengeable { class RootWrapper: public QObject, virtual public Scavengeable {
Q_OBJECT; Q_OBJECT;
public: public:
explicit RootWrapper(QUrl rootUrl); explicit RootWrapper(QString rootPath);
void reloadGraph(bool hard);
void changeRoot(QtShell* newRoot);
QObject* scavengeTargetFor(QObject* child) override; QObject* scavengeTargetFor(QObject* child) override;
void reloadGraph(bool hard);
private slots: private slots:
void destroy(); void onConfigChanged();
void onWatchedFilesChanged();
private: private:
QUrl rootUrl; QString rootPath;
QQmlEngine engine; QQmlEngine engine;
QtShell* activeRoot = nullptr; ShellRoot* root = nullptr;
QMetaObject::Connection destroyConnection; FiletreeWatcher* configWatcher = nullptr;
}; };

View file

@ -3,16 +3,17 @@
#include <qobject.h> #include <qobject.h>
#include <qqmllist.h> #include <qqmllist.h>
#include <qtmetamacros.h>
void QtShell::earlyInit(QObject* old) { void ShellRoot::earlyInit(QObject* old) {
auto* oldshell = qobject_cast<QtShell*>(old); auto* oldshell = qobject_cast<ShellRoot*>(old);
if (oldshell != nullptr) { if (oldshell != nullptr) {
this->scavengeableChildren = std::move(oldshell->children); this->scavengeableChildren = std::move(oldshell->children);
} }
} }
QObject* QtShell::scavengeTargetFor(QObject* /* child */) { QObject* ShellRoot::scavengeTargetFor(QObject* /* child */) {
if (this->scavengeableChildren.length() > this->children.length()) { if (this->scavengeableChildren.length() > this->children.length()) {
return this->scavengeableChildren[this->children.length()]; return this->scavengeableChildren[this->children.length()];
} }
@ -20,11 +21,19 @@ QObject* QtShell::scavengeTargetFor(QObject* /* child */) {
return nullptr; return nullptr;
} }
QQmlListProperty<QObject> QtShell::components() { void ShellRoot::setConfig(ShellConfig config) {
this->mConfig = config;
emit this->configChanged();
}
ShellConfig ShellRoot::config() const { return this->mConfig; }
QQmlListProperty<QObject> ShellRoot::components() {
return QQmlListProperty<QObject>( return QQmlListProperty<QObject>(
this, this,
nullptr, nullptr,
&QtShell::appendComponent, &ShellRoot::appendComponent,
nullptr, nullptr,
nullptr, nullptr,
nullptr, nullptr,
@ -33,8 +42,8 @@ QQmlListProperty<QObject> QtShell::components() {
); );
} }
void QtShell::appendComponent(QQmlListProperty<QObject>* list, QObject* component) { void ShellRoot::appendComponent(QQmlListProperty<QObject>* list, QObject* component) {
auto* shell = static_cast<QtShell*>(list->object); // NOLINT auto* shell = static_cast<ShellRoot*>(list->object); // NOLINT
component->setParent(shell); component->setParent(shell);
shell->children.append(component); shell->children.append(component);
} }

View file

@ -9,24 +9,40 @@
#include "scavenge.hpp" #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_OBJECT;
Q_PROPERTY(ShellConfig config READ config WRITE setConfig);
Q_PROPERTY(QQmlListProperty<QObject> components READ components FINAL); Q_PROPERTY(QQmlListProperty<QObject> components READ components FINAL);
Q_CLASSINFO("DefaultProperty", "components"); Q_CLASSINFO("DefaultProperty", "components");
QML_ELEMENT; QML_ELEMENT;
public: public:
explicit QtShell(QObject* parent = nullptr): Scavenger(parent) {} explicit ShellRoot(QObject* parent = nullptr): Scavenger(parent) {}
void earlyInit(QObject* old) override; void earlyInit(QObject* old) override;
QObject* scavengeTargetFor(QObject* child) override; QObject* scavengeTargetFor(QObject* child) override;
void setConfig(ShellConfig config);
[[nodiscard]] ShellConfig config() const;
QQmlListProperty<QObject> components(); QQmlListProperty<QObject> components();
signals:
void configChanged();
private: private:
static void appendComponent(QQmlListProperty<QObject>* list, QObject* component); static void appendComponent(QQmlListProperty<QObject>* list, QObject* component);
public: ShellConfig mConfig;
// track only the children assigned to `components` in order // track only the children assigned to `components` in order
QList<QObject*> children; QList<QObject*> children;
QList<QObject*> scavengeableChildren; QList<QObject*> scavengeableChildren;

38
src/cpp/watcher.cpp Normal file
View file

@ -0,0 +1,38 @@
#include "watcher.hpp"
#include <qdir.h>
#include <qfileinfo.h>
#include <qfilesystemwatcher.h>
#include <qobject.h>
#include <qtmetamacros.h>
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); }

24
src/cpp/watcher.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <qdir.h>
#include <qfilesystemwatcher.h>
#include <qobject.h>
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;
};