Compare commits
8 commits
c7fffd034c
...
56502b79f6
Author | SHA1 | Date | |
---|---|---|---|
outfoxxed | 56502b79f6 | ||
outfoxxed | b62f2f3a50 | ||
outfoxxed | f2f7ec92f2 | ||
outfoxxed | e1281b8e7b | ||
outfoxxed | 362789fc46 | ||
outfoxxed | ba1e18a125 | ||
outfoxxed | 9a5ad44aa9 | ||
outfoxxed | d14258df8e |
|
@ -25,6 +25,11 @@ qt_standard_project_setup(REQUIRES 6.6)
|
|||
qt_add_executable(qtshell
|
||||
src/cpp/main.cpp
|
||||
src/cpp/shell.cpp
|
||||
src/cpp/variants.cpp
|
||||
src/cpp/rootwrapper.cpp
|
||||
src/cpp/proxywindow.cpp
|
||||
src/cpp/scavenge.cpp
|
||||
src/cpp/rootwrapper.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(qtshell URI QtShell)
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
#include <qguiapplication.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlapplicationengine.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qstandardpaths.h>
|
||||
#include <qstring.h>
|
||||
|
||||
#include "shell.hpp"
|
||||
#include "rootwrapper.hpp"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const auto app = QGuiApplication(argc, argv);
|
||||
|
@ -40,24 +40,12 @@ int main(int argc, char** argv) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
CONFIG_PATH = configPath;
|
||||
|
||||
LayerShellQt::Shell::useLayerShell();
|
||||
// Base window transparency appears to be additive.
|
||||
// Use a fully transparent window with a colored rect.
|
||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||
|
||||
auto engine = QQmlApplicationEngine();
|
||||
engine.load(configPath);
|
||||
|
||||
if (engine.rootObjects().isEmpty()) {
|
||||
qCritical() << "failed to load config file";
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto* shellobj = qobject_cast<QtShell*>(engine.rootObjects().first());
|
||||
|
||||
if (shellobj == nullptr) {
|
||||
qCritical() << "root item was not a QtShell";
|
||||
return -1;
|
||||
}
|
||||
auto root = RootWrapper(configPath);
|
||||
|
||||
return QGuiApplication::exec();
|
||||
}
|
||||
|
|
121
src/cpp/proxywindow.cpp
Normal file
121
src/cpp/proxywindow.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
#include "proxywindow.hpp"
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
ProxyWindowBase::~ProxyWindowBase() {
|
||||
if (this->window != nullptr) {
|
||||
this->window->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyWindowBase::earlyInit(QObject* old) {
|
||||
auto* oldpw = qobject_cast<ProxyWindowBase*>(old);
|
||||
|
||||
if (oldpw == nullptr || oldpw->window == nullptr) {
|
||||
this->window = new QQuickWindow();
|
||||
} else {
|
||||
this->window = oldpw->disownWindow();
|
||||
}
|
||||
}
|
||||
|
||||
QQuickWindow* ProxyWindowBase::disownWindow() {
|
||||
auto data = this->data();
|
||||
ProxyWindowBase::dataClear(&data);
|
||||
data.clear(&data);
|
||||
|
||||
auto* window = this->window;
|
||||
this->window = nullptr;
|
||||
return window;
|
||||
}
|
||||
|
||||
QQuickItem* ProxyWindowBase::item() { return this->window->contentItem(); }
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PROXYPROP(type, get, set) \
|
||||
type ProxyWindowBase::get() { return this->window->get(); } \
|
||||
void ProxyWindowBase::set(type value) { this->window->set(value); }
|
||||
|
||||
PROXYPROP(bool, isVisible, setVisible);
|
||||
PROXYPROP(qint32, width, setWidth);
|
||||
PROXYPROP(qint32, height, setHeight);
|
||||
PROXYPROP(QColor, color, setColor);
|
||||
|
||||
// see:
|
||||
// https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quick/items/qquickwindow.cpp
|
||||
// https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quick/items/qquickitem.cpp
|
||||
//
|
||||
// relevant functions are private so we call them via the property
|
||||
|
||||
QQmlListProperty<QObject> ProxyWindowBase::data() {
|
||||
return QQmlListProperty<QObject>(
|
||||
this,
|
||||
nullptr,
|
||||
ProxyWindowBase::dataAppend,
|
||||
ProxyWindowBase::dataCount,
|
||||
ProxyWindowBase::dataAt,
|
||||
ProxyWindowBase::dataClear,
|
||||
ProxyWindowBase::dataReplace,
|
||||
ProxyWindowBase::dataRemoveLast
|
||||
);
|
||||
}
|
||||
|
||||
QQmlListProperty<QObject> ProxyWindowBase::dataBacker(QQmlListProperty<QObject>* prop) {
|
||||
auto* that = static_cast<ProxyWindowBase*>(prop->object); // NOLINT
|
||||
return that->window->property("data").value<QQmlListProperty<QObject>>();
|
||||
}
|
||||
|
||||
void ProxyWindowBase::dataAppend(QQmlListProperty<QObject>* prop, QObject* obj) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.append(&backer, obj);
|
||||
}
|
||||
|
||||
qsizetype ProxyWindowBase::dataCount(QQmlListProperty<QObject>* prop) {
|
||||
auto backer = dataBacker(prop);
|
||||
return backer.count(&backer);
|
||||
}
|
||||
|
||||
QObject* ProxyWindowBase::dataAt(QQmlListProperty<QObject>* prop, qsizetype i) {
|
||||
auto backer = dataBacker(prop);
|
||||
return backer.at(&backer, i);
|
||||
}
|
||||
|
||||
void ProxyWindowBase::dataClear(QQmlListProperty<QObject>* prop) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.clear(&backer);
|
||||
}
|
||||
|
||||
void ProxyWindowBase::dataReplace(QQmlListProperty<QObject>* prop, qsizetype i, QObject* obj) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.replace(&backer, i, obj);
|
||||
}
|
||||
|
||||
void ProxyWindowBase::dataRemoveLast(QQmlListProperty<QObject>* prop) {
|
||||
auto backer = dataBacker(prop);
|
||||
backer.removeLast(&backer);
|
||||
}
|
||||
|
||||
void ProxyFloatingWindow::earlyInit(QObject* old) {
|
||||
ProxyWindowBase::earlyInit(old);
|
||||
this->geometryLocked = this->window->isVisible();
|
||||
}
|
||||
|
||||
void ProxyFloatingWindow::componentComplete() {
|
||||
ProxyWindowBase::componentComplete();
|
||||
this->geometryLocked = true;
|
||||
}
|
||||
|
||||
void ProxyFloatingWindow::setWidth(qint32 value) {
|
||||
if (!this->geometryLocked) {
|
||||
ProxyWindowBase::setWidth(value);
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyFloatingWindow::setHeight(qint32 value) {
|
||||
if (!this->geometryLocked) {
|
||||
ProxyWindowBase::setHeight(value);
|
||||
}
|
||||
}
|
88
src/cpp/proxywindow.hpp
Normal file
88
src/cpp/proxywindow.hpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
|
||||
// Proxy to an actual window exposing a limited property set with the ability to
|
||||
// transfer it to a new window.
|
||||
// Detaching a window and touching any property is a use after free.
|
||||
//
|
||||
// NOTE: setting an `id` in qml will point to the proxy window and not the real window so things
|
||||
// like anchors must use `item`.
|
||||
class ProxyWindowBase: public Scavenger {
|
||||
Q_OBJECT;
|
||||
Q_PROPERTY(QQuickItem* item READ item CONSTANT);
|
||||
Q_PROPERTY(bool visible READ isVisible WRITE setVisible);
|
||||
Q_PROPERTY(qint32 width READ width WRITE setWidth);
|
||||
Q_PROPERTY(qint32 height READ height WRITE setHeight);
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor);
|
||||
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
||||
Q_CLASSINFO("DefaultProperty", "data");
|
||||
|
||||
protected:
|
||||
void earlyInit(QObject* old) override;
|
||||
|
||||
public:
|
||||
explicit ProxyWindowBase(QObject* parent = nullptr): Scavenger(parent) {}
|
||||
~ProxyWindowBase() override;
|
||||
|
||||
ProxyWindowBase(ProxyWindowBase&) = delete;
|
||||
ProxyWindowBase(ProxyWindowBase&&) = delete;
|
||||
void operator=(ProxyWindowBase&) = delete;
|
||||
void operator=(ProxyWindowBase&&) = delete;
|
||||
|
||||
// Disown the backing window and delete all its children.
|
||||
QQuickWindow* disownWindow();
|
||||
|
||||
QQuickItem* item();
|
||||
|
||||
bool isVisible();
|
||||
virtual void setVisible(bool value);
|
||||
|
||||
qint32 width();
|
||||
virtual void setWidth(qint32 value);
|
||||
|
||||
qint32 height();
|
||||
virtual void setHeight(qint32 value);
|
||||
|
||||
QColor color();
|
||||
void setColor(QColor value);
|
||||
|
||||
QQmlListProperty<QObject> data();
|
||||
|
||||
private:
|
||||
static QQmlListProperty<QObject> dataBacker(QQmlListProperty<QObject>* prop);
|
||||
static void dataAppend(QQmlListProperty<QObject>* prop, QObject* obj);
|
||||
static qsizetype dataCount(QQmlListProperty<QObject>* prop);
|
||||
static QObject* dataAt(QQmlListProperty<QObject>* prop, qsizetype i);
|
||||
static void dataClear(QQmlListProperty<QObject>* prop);
|
||||
static void dataReplace(QQmlListProperty<QObject>* prop, qsizetype i, QObject* obj);
|
||||
static void dataRemoveLast(QQmlListProperty<QObject>* prop);
|
||||
|
||||
QQuickWindow* window = nullptr;
|
||||
};
|
||||
|
||||
// qt attempts to resize the window but fails because wayland
|
||||
// and only resizes the graphics context which looks terrible.
|
||||
class ProxyFloatingWindow: public ProxyWindowBase {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
void earlyInit(QObject* old) override;
|
||||
void componentComplete() override;
|
||||
|
||||
void setWidth(qint32 value) override;
|
||||
void setHeight(qint32 value) override;
|
||||
|
||||
private:
|
||||
bool geometryLocked = false;
|
||||
};
|
72
src/cpp/rootwrapper.cpp
Normal file
72
src/cpp/rootwrapper.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include "rootwrapper.hpp"
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qurl.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
#include "shell.hpp"
|
||||
|
||||
RootWrapper::RootWrapper(QUrl rootUrl):
|
||||
QObject(nullptr), rootUrl(std::move(rootUrl)), engine(this) {
|
||||
this->reloadGraph(true);
|
||||
|
||||
if (this->activeRoot == nullptr) {
|
||||
qCritical() << "could not create scene graph, exiting";
|
||||
exit(-1); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
void RootWrapper::reloadGraph(bool hard) {
|
||||
if (this->activeRoot != nullptr) {
|
||||
this->engine.clearComponentCache();
|
||||
}
|
||||
|
||||
auto component = QQmlComponent(&this->engine, this->rootUrl);
|
||||
|
||||
SCAVENGE_PARENT = hard ? nullptr : this;
|
||||
auto* obj = component.beginCreate(this->engine.rootContext());
|
||||
SCAVENGE_PARENT = nullptr;
|
||||
|
||||
if (obj == nullptr) {
|
||||
qWarning() << component.errorString().toStdString().c_str();
|
||||
qWarning() << "failed to create root component";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* newRoot = qobject_cast<QtShell*>(obj);
|
||||
if (newRoot == nullptr) {
|
||||
qWarning() << "root component was not a QtShell";
|
||||
delete obj;
|
||||
return;
|
||||
}
|
||||
|
||||
component.completeCreate();
|
||||
|
||||
if (this->activeRoot != nullptr) {
|
||||
this->activeRoot->deleteLater();
|
||||
this->activeRoot = nullptr;
|
||||
}
|
||||
|
||||
this->activeRoot = newRoot;
|
||||
}
|
||||
|
||||
void RootWrapper::changeRoot(QtShell* newRoot) {
|
||||
if (this->activeRoot != nullptr) {
|
||||
QObject::disconnect(this->destroyConnection);
|
||||
this->activeRoot->deleteLater();
|
||||
}
|
||||
|
||||
if (newRoot != nullptr) {
|
||||
this->activeRoot = newRoot;
|
||||
QObject::connect(this->activeRoot, &QtShell::destroyed, this, &RootWrapper::destroy);
|
||||
}
|
||||
}
|
||||
|
||||
QObject* RootWrapper::scavengeTargetFor(QObject* /* child */) { return this->activeRoot; }
|
||||
|
||||
void RootWrapper::destroy() { this->deleteLater(); }
|
31
src/cpp/rootwrapper.hpp
Normal file
31
src/cpp/rootwrapper.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qurl.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
#include "shell.hpp"
|
||||
|
||||
class RootWrapper: public QObject, virtual public Scavengeable {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit RootWrapper(QUrl rootUrl);
|
||||
|
||||
void reloadGraph(bool hard);
|
||||
void changeRoot(QtShell* newRoot);
|
||||
|
||||
QObject* scavengeTargetFor(QObject* child) override;
|
||||
|
||||
private slots:
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
QUrl rootUrl;
|
||||
QQmlEngine engine;
|
||||
QtShell* activeRoot = nullptr;
|
||||
QMetaObject::Connection destroyConnection;
|
||||
};
|
45
src/cpp/scavenge.cpp
Normal file
45
src/cpp/scavenge.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "scavenge.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
|
||||
QObject* SCAVENGE_PARENT = nullptr; // NOLINT
|
||||
|
||||
void Scavenger::classBegin() {
|
||||
// prayers
|
||||
if (this->parent() == nullptr) {
|
||||
this->setParent(SCAVENGE_PARENT);
|
||||
SCAVENGE_PARENT = nullptr;
|
||||
}
|
||||
|
||||
auto* parent = dynamic_cast<Scavengeable*>(this->parent());
|
||||
|
||||
QObject* old = nullptr;
|
||||
if (parent != nullptr) {
|
||||
old = parent->scavengeTargetFor(this);
|
||||
}
|
||||
|
||||
this->earlyInit(old);
|
||||
}
|
||||
|
||||
QObject* createComponentScavengeable(
|
||||
QObject& parent,
|
||||
QQmlComponent& component,
|
||||
QVariantMap& initialProperties
|
||||
) {
|
||||
SCAVENGE_PARENT = &parent;
|
||||
auto* instance = component.beginCreate(QQmlEngine::contextForObject(&parent));
|
||||
SCAVENGE_PARENT = nullptr;
|
||||
if (instance == nullptr) return nullptr;
|
||||
if (instance->parent() != nullptr) instance->setParent(&parent);
|
||||
component.setInitialProperties(instance, initialProperties);
|
||||
component.completeCreate();
|
||||
|
||||
if (instance == nullptr) {
|
||||
qWarning() << component.errorString().toStdString().c_str();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
49
src/cpp/scavenge.hpp
Normal file
49
src/cpp/scavenge.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
extern QObject* SCAVENGE_PARENT; // NOLINT
|
||||
|
||||
class Scavenger: public QObject, public QQmlParserStatus {
|
||||
Q_OBJECT;
|
||||
Q_INTERFACES(QQmlParserStatus);
|
||||
|
||||
public:
|
||||
explicit Scavenger(QObject* parent = nullptr): QObject(parent) {}
|
||||
~Scavenger() override = default;
|
||||
|
||||
Scavenger(Scavenger&) = delete;
|
||||
Scavenger(Scavenger&&) = delete;
|
||||
void operator=(Scavenger&) = delete;
|
||||
void operator=(Scavenger&&) = delete;
|
||||
|
||||
void classBegin() override;
|
||||
void componentComplete() override {}
|
||||
|
||||
protected:
|
||||
// do early init, sometimes with a scavengeable target
|
||||
virtual void earlyInit(QObject* old) = 0;
|
||||
};
|
||||
|
||||
class Scavengeable {
|
||||
public:
|
||||
Scavengeable() = default;
|
||||
virtual ~Scavengeable() = default;
|
||||
|
||||
Scavengeable(Scavengeable&) = delete;
|
||||
Scavengeable(Scavengeable&&) = delete;
|
||||
void operator=(Scavengeable&) = delete;
|
||||
void operator=(Scavengeable&&) = delete;
|
||||
|
||||
// return an old object that might have salvageable resources
|
||||
virtual QObject* scavengeTargetFor(QObject* child) = 0;
|
||||
};
|
||||
|
||||
QObject* createComponentScavengeable(
|
||||
QObject& parent,
|
||||
QQmlComponent& component,
|
||||
QVariantMap& initialProperties
|
||||
);
|
|
@ -1,35 +1,44 @@
|
|||
#include "shell.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qfileinfo.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlcontext.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
QString CONFIG_PATH; // NOLINT
|
||||
#include "rootwrapper.hpp"
|
||||
|
||||
QtShell::QtShell(): QObject(nullptr), path(CONFIG_PATH), dir(QFileInfo(this->path).dir()) {
|
||||
CONFIG_PATH = "";
|
||||
}
|
||||
void QtShell::reload(bool hard) {
|
||||
auto* rootobj = QQmlEngine::contextForObject(this)->engine()->parent();
|
||||
auto* root = qobject_cast<RootWrapper*>(rootobj);
|
||||
|
||||
void QtShell::componentComplete() {
|
||||
if (this->path.isEmpty()) {
|
||||
qWarning() << "Multiple QtShell objects were created. You should not do this.";
|
||||
if (root == nullptr) {
|
||||
qWarning() << "cannot find RootWrapper for reload, ignoring request";
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto* component: this->mComponents) {
|
||||
component->prepare(this->dir);
|
||||
root->reloadGraph(hard);
|
||||
}
|
||||
|
||||
void QtShell::earlyInit(QObject* old) {
|
||||
auto* oldshell = qobject_cast<QtShell*>(old);
|
||||
|
||||
if (oldshell != nullptr) {
|
||||
this->scavengeableChildren = std::move(oldshell->children);
|
||||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<ShellComponent> QtShell::components() {
|
||||
return QQmlListProperty<ShellComponent>(
|
||||
QObject* QtShell::scavengeTargetFor(QObject* /* child */) {
|
||||
if (this->scavengeableChildren.length() > this->children.length()) {
|
||||
return this->scavengeableChildren[this->children.length()];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QQmlListProperty<QObject> QtShell::components() {
|
||||
return QQmlListProperty<QObject>(
|
||||
this,
|
||||
nullptr,
|
||||
&QtShell::appendComponent,
|
||||
|
@ -41,56 +50,8 @@ QQmlListProperty<ShellComponent> QtShell::components() {
|
|||
);
|
||||
}
|
||||
|
||||
void QtShell::appendComponent(QQmlListProperty<ShellComponent>* list, ShellComponent* component) {
|
||||
auto* shell = qobject_cast<QtShell*>(list->object);
|
||||
|
||||
if (shell != nullptr) shell->mComponents.append(component);
|
||||
}
|
||||
|
||||
void ShellComponent::setSource(QString source) {
|
||||
if (this->mComponent != nullptr) {
|
||||
qWarning() << "cannot define ShellComponent.source while defining a component";
|
||||
return;
|
||||
}
|
||||
|
||||
this->mSource = std::move(source);
|
||||
}
|
||||
|
||||
void ShellComponent::setComponent(QQmlComponent* component) {
|
||||
if (this->mSource != nullptr) {
|
||||
qWarning() << "cannot define a component for ShellComponent while source is set";
|
||||
return;
|
||||
}
|
||||
|
||||
this->mComponent = component;
|
||||
}
|
||||
|
||||
void ShellComponent::prepare(const QDir& basePath) {
|
||||
if (this->mComponent == nullptr) {
|
||||
if (this->mSource == nullptr) {
|
||||
qWarning() << "neither source or a component was set for ShellComponent on prepare";
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = basePath.filePath(this->mSource);
|
||||
qDebug() << "preparing ShellComponent from" << path;
|
||||
|
||||
auto* context = QQmlEngine::contextForObject(this);
|
||||
if (context == nullptr) {
|
||||
qWarning() << "ShellComponent was created without an associated QQmlEngine, cannot prepare";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* engine = context->engine();
|
||||
|
||||
this->mComponent = new QQmlComponent(engine, path, this);
|
||||
|
||||
if (this->mComponent == nullptr) {
|
||||
qWarning() << "could not load ShellComponent source" << path;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Sending ready for ShellComponent";
|
||||
emit this->ready();
|
||||
void QtShell::appendComponent(QQmlListProperty<QObject>* list, QObject* component) {
|
||||
auto* shell = static_cast<QtShell*>(list->object); // NOLINT
|
||||
component->setParent(shell);
|
||||
shell->children.append(component);
|
||||
}
|
||||
|
|
|
@ -1,59 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
extern QString CONFIG_PATH; // NOLINT
|
||||
#include "scavenge.hpp"
|
||||
|
||||
class ShellComponent;
|
||||
|
||||
class QtShell: public QObject, public QQmlParserStatus {
|
||||
class QtShell: public Scavenger, virtual public Scavengeable {
|
||||
Q_OBJECT;
|
||||
Q_INTERFACES(QQmlParserStatus);
|
||||
Q_PROPERTY(QQmlListProperty<ShellComponent> components READ components FINAL);
|
||||
Q_PROPERTY(QQmlListProperty<QObject> components READ components FINAL);
|
||||
Q_CLASSINFO("DefaultProperty", "components");
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit QtShell();
|
||||
explicit QtShell(QObject* parent = nullptr): Scavenger(parent) {}
|
||||
|
||||
void classBegin() override {};
|
||||
void componentComplete() override;
|
||||
void earlyInit(QObject* old) override;
|
||||
QObject* scavengeTargetFor(QObject* child) override;
|
||||
|
||||
QQmlListProperty<ShellComponent> components();
|
||||
QQmlListProperty<QObject> components();
|
||||
|
||||
public slots:
|
||||
void reload(bool hard = true);
|
||||
|
||||
private:
|
||||
static void appendComponent(QQmlListProperty<ShellComponent>* list, ShellComponent* component);
|
||||
|
||||
QList<ShellComponent*> mComponents;
|
||||
QString path;
|
||||
QDir dir;
|
||||
};
|
||||
|
||||
class ShellComponent: public QObject {
|
||||
Q_OBJECT;
|
||||
Q_PROPERTY(QString source WRITE setSource);
|
||||
Q_PROPERTY(QQmlComponent* component MEMBER mComponent WRITE setComponent);
|
||||
Q_CLASSINFO("DefaultProperty", "component");
|
||||
QML_ELEMENT;
|
||||
static void appendComponent(QQmlListProperty<QObject>* list, QObject* component);
|
||||
|
||||
public:
|
||||
explicit ShellComponent(QObject* parent = nullptr): QObject(parent) {}
|
||||
|
||||
void setSource(QString source);
|
||||
void setComponent(QQmlComponent* component);
|
||||
void prepare(const QDir& basePath);
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
|
||||
private:
|
||||
QString mSource;
|
||||
QQmlComponent* mComponent = nullptr;
|
||||
// track only the children assigned to `components` in order
|
||||
QList<QObject*> children;
|
||||
QList<QObject*> scavengeableChildren;
|
||||
};
|
||||
|
|
148
src/cpp/variants.cpp
Normal file
148
src/cpp/variants.cpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#include "variants.hpp"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
|
||||
void Variants::earlyInit(QObject* old) {
|
||||
auto* oldv = qobject_cast<Variants*>(old);
|
||||
if (oldv != nullptr) {
|
||||
this->scavengeableInstances = std::move(oldv->instances);
|
||||
}
|
||||
}
|
||||
|
||||
QObject* Variants::scavengeTargetFor(QObject* /* child */) {
|
||||
// Attempt to find the set that most closely matches the current one.
|
||||
// This is biased to the order of the scavenge list which should help in
|
||||
// case of conflicts as long as variants have not been reordered.
|
||||
|
||||
if (this->activeScavengeVariant != nullptr) {
|
||||
auto& values = this->scavengeableInstances.values;
|
||||
if (values.empty()) return nullptr;
|
||||
|
||||
int matchcount = 0;
|
||||
int matchi = 0;
|
||||
int i = 0;
|
||||
for (auto& [valueSet, _]: values) {
|
||||
int count = 0;
|
||||
for (auto& [k, v]: this->activeScavengeVariant->toStdMap()) {
|
||||
if (valueSet.contains(k) && valueSet.value(k) == v) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > matchcount) {
|
||||
matchcount = count;
|
||||
matchi = i;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (matchcount > 0) {
|
||||
return values.takeAt(matchi).second;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Variants::setVariants(QVariantList variants) {
|
||||
this->mVariants = std::move(variants);
|
||||
this->updateVariants();
|
||||
}
|
||||
|
||||
void Variants::componentComplete() {
|
||||
Scavenger::componentComplete();
|
||||
this->updateVariants();
|
||||
}
|
||||
|
||||
void Variants::updateVariants() {
|
||||
if (this->mComponent == nullptr) {
|
||||
qWarning() << "Variants instance does not have a component specified";
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up removed entries
|
||||
for (auto iter = this->instances.values.begin(); iter < this->instances.values.end();) {
|
||||
if (this->mVariants.contains(iter->first)) {
|
||||
iter++;
|
||||
} else {
|
||||
iter->second->deleteLater();
|
||||
iter = this->instances.values.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter = this->mVariants.begin(); iter < this->mVariants.end(); iter++) {
|
||||
auto& variantObj = *iter;
|
||||
if (!variantObj.canConvert<QVariantMap>()) {
|
||||
qWarning() << "value passed to Variants is not an object and will be ignored:" << variantObj;
|
||||
} else {
|
||||
auto variant = variantObj.value<QVariantMap>();
|
||||
|
||||
for (auto iter2 = this->mVariants.begin(); iter2 < iter; iter2++) {
|
||||
if (*iter2 == variantObj) {
|
||||
qWarning() << "same value specified twice in Variants, duplicates will be ignored:"
|
||||
<< variantObj;
|
||||
goto outer;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->instances.contains(variant)) {
|
||||
continue; // we dont need to recreate this one
|
||||
}
|
||||
|
||||
this->activeScavengeVariant = &variant;
|
||||
auto* instance = createComponentScavengeable(*this, *this->mComponent, variant);
|
||||
|
||||
if (instance == nullptr) {
|
||||
qWarning() << "failed to create variant with object" << variant;
|
||||
continue;
|
||||
}
|
||||
|
||||
this->instances.insert(variant, instance);
|
||||
}
|
||||
|
||||
outer:;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool AwfulMap<K, V>::contains(const K& key) const {
|
||||
return std::ranges::any_of(this->values, [&](const QPair<K, V>& pair) {
|
||||
return pair.first == key;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
V* AwfulMap<K, V>::get(const K& key) {
|
||||
for (auto& [k, v]: this->values) {
|
||||
if (key == k) {
|
||||
return &v;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void AwfulMap<K, V>::insert(K key, V value) {
|
||||
this->values.push_back(QPair<K, V>(key, value));
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool AwfulMap<K, V>::remove(const K& key) {
|
||||
for (auto iter = this->values.begin(); iter < this->values.end(); iter++) {
|
||||
if (iter->first == key) {
|
||||
this->values.erase(iter);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
50
src/cpp/variants.hpp
Normal file
50
src/cpp/variants.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qmap.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
|
||||
#include "scavenge.hpp"
|
||||
|
||||
// extremely inefficient map
|
||||
template <typename K, typename V>
|
||||
class AwfulMap {
|
||||
public:
|
||||
[[nodiscard]] bool contains(const K& key) const;
|
||||
[[nodiscard]] V* get(const K& key);
|
||||
void insert(K key, V value); // assumes no duplicates
|
||||
bool remove(const K& key); // returns true if anything was removed
|
||||
QList<QPair<K, V>> values;
|
||||
};
|
||||
|
||||
class Variants: public Scavenger, virtual public Scavengeable {
|
||||
Q_OBJECT;
|
||||
Q_PROPERTY(QQmlComponent* component MEMBER mComponent);
|
||||
Q_PROPERTY(QVariantList variants MEMBER mVariants WRITE setVariants);
|
||||
Q_CLASSINFO("DefaultProperty", "component");
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit Variants(QObject* parent = nullptr): Scavenger(parent) {}
|
||||
|
||||
void earlyInit(QObject* old) override;
|
||||
QObject* scavengeTargetFor(QObject* child) override;
|
||||
|
||||
void componentComplete() override;
|
||||
|
||||
private:
|
||||
void setVariants(QVariantList variants);
|
||||
void updateVariants();
|
||||
|
||||
QQmlComponent* mComponent = nullptr;
|
||||
QVariantList mVariants;
|
||||
AwfulMap<QVariantMap, QObject*> instances;
|
||||
|
||||
// pointers may die post componentComplete.
|
||||
AwfulMap<QVariantMap, QObject*> scavengeableInstances;
|
||||
QVariantMap* activeScavengeVariant = nullptr;
|
||||
};
|
Loading…
Reference in a new issue