├── .gitignore ├── LICENSE ├── README.md ├── README.pt_BR.md ├── demo.gif ├── example ├── components │ ├── Nested1.qml │ └── Nested2.qml ├── example.pro ├── main.cpp ├── main.qml └── qml.qrc └── qmlreloader ├── filewatcher.cpp ├── filewatcher.h ├── qmlfileinterceptor.cpp ├── qmlfileinterceptor.h ├── qmlreloader.cpp ├── qmlreloader.h └── qmlreloader.pri /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Arthur Turrini. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QML reLoader 2 | 3 | 4 | English | [Português (Brasil)](./README.pt_BR.md) 5 | 6 | ### Overview 7 | The **QML reLoader** allows developers to quickly reload the user interface on every document save by watching the hierarchy of loaded files when using the Qt's Loader component. The main difference between this and other solutions is that we will not reload the entire application, but only the parts that have been changed (and their dependencies). It is aimed to speed up development in small to big projects. 8 | 9 | It supports Linux, macOS and Windows. It has been tested with Qt 5.12 through 5.15. MIT Licensed. 10 | 11 | 12 | ## Demo 13 | 14 | ![QML reLoader in action](demo.gif) 15 | 16 | 17 | 18 | ## How to use it 19 | 20 | There are three steps: 21 | 22 | **1:** Add the folder **qmlreloader/** to your project and include the **qmlreloader.pri** in your ".pro"ject file: 23 | 24 | 25 | ```qmake 26 | QT += quick 27 | 28 | CONFIG += c++11 silent 29 | 30 | INCLUDEPATH += $$PWD 31 | 32 | SOURCES += \ 33 | $$PWD/main.cpp 34 | 35 | RESOURCES += \ 36 | $$PWD/qml.qrc 37 | 38 | # QML reLoader 39 | include($$PWD/../qmlreloader/qmlreloader.pri)``` 40 | ``` 41 | 42 | 43 | **2:** Include the header **qmlreloader.h** in your main.cpp, and after the Engine declaration, register it with the *QML reLoader*: 44 | 45 | ```c++ 46 | #include 47 | #include 48 | 49 | /// Include the header 50 | #include "qmlreloader.h" 51 | 52 | int main(int argc, char *argv[]) 53 | { 54 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 55 | 56 | QGuiApplication app(argc, argv); 57 | 58 | QQmlApplicationEngine engine; 59 | 60 | /// Register the engine within QML reLoader 61 | QMLreLoader::QMLreLoader reloader(&engine); 62 | 63 | const QUrl url(QStringLiteral("qrc:/main.qml")); 64 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 65 | &app, [url](QObject *obj, const QUrl &objUrl) { 66 | if (!obj && url == objUrl) 67 | QCoreApplication::exit(-1); 68 | }, Qt::QueuedConnection); 69 | engine.load(url); 70 | 71 | return app.exec(); 72 | } 73 | ``` 74 | 75 | **3:** On every usage of the traditional *Qt QML Loader*, register it within *QML reLoader* on the *Component.onCompleted {}* signal, and you're done: 76 | 77 | ```qml 78 | Loader { 79 | id: loadCRM 80 | 81 | anchors.fill: parent 82 | source: "qrc:/modules/crm/StartCRM.qml" 83 | 84 | Component.onCompleted: { 85 | reloader.registerLoader(loadCRM); // this loader will now be monitored 86 | } 87 | } 88 | ``` 89 | 90 | Copyright (c) 2020 Arthur Turrini. 91 | 92 | -------------------------------------------------------------------------------- /README.pt_BR.md: -------------------------------------------------------------------------------- 1 | # QML reLoader 2 | 3 | 4 | Português | [English (United States)](./README.md) 5 | 6 | ### Overview 7 | O **QML reLoader** permite aos desenvolvedores para rapidamente recarregar a interface do usuário a cada documento salvo, por monitorar a hierarquia dos arquivos carregados quando se usa o componente Loader do Qt. A principal diferença desta para as outras soluções é que nós não iremos recarregar o aplicativo inteiro, mas apenas as partes que foram modificadas (e suas dependências). É voltado para acelerar o desenvolvimento de projetos, dos pequenos aos grandes. 8 | 9 | Suporta Linux, macOS e Windows. Foi testado com Qt 5.12 ao 5.15. Licença utilizada: MIT. 10 | 11 | 12 | ## Demonstração 13 | 14 | ![QML reLoader em ação](demo.gif) 15 | 16 | 17 | 18 | ## Como usar 19 | 20 | Há apenas 3 passos: 21 | 22 | **1:** Adicione as pasta **qmlreloader/** ao seu projeto e inclua o **qmlreloader.pri** no seu arquivo ".pro", como a seguir: 23 | 24 | 25 | ```qmake 26 | QT += quick 27 | 28 | CONFIG += c++11 silent 29 | 30 | INCLUDEPATH += $$PWD 31 | 32 | SOURCES += \ 33 | $$PWD/main.cpp 34 | 35 | RESOURCES += \ 36 | $$PWD/qml.qrc 37 | 38 | # QML reLoader 39 | include($$PWD/../qmlreloader/qmlreloader.pri)``` 40 | ``` 41 | 42 | 43 | **2:** Depois inclua o cabeçalho **qmlreloader.h** no seu main.cpp, e depois da declaração da engine, registre-a no *QML reLoader*: 44 | 45 | ```c++ 46 | #include 47 | #include 48 | 49 | /// Inclua o cabeçalho 50 | #include "qmlreloader.h" 51 | 52 | int main(int argc, char *argv[]) 53 | { 54 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 55 | 56 | QGuiApplication app(argc, argv); 57 | 58 | QQmlApplicationEngine engine; 59 | 60 | /// Registre a engine QML com o QML reLoader 61 | QMLreLoader::QMLreLoader reloader(&engine); 62 | 63 | const QUrl url(QStringLiteral("qrc:/main.qml")); 64 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 65 | &app, [url](QObject *obj, const QUrl &objUrl) { 66 | if (!obj && url == objUrl) 67 | QCoreApplication::exit(-1); 68 | }, Qt::QueuedConnection); 69 | engine.load(url); 70 | 71 | return app.exec(); 72 | } 73 | ``` 74 | 75 | **3:** E então para casa uso do tradicional *Loader do QML*, registre-o no *QML reLoader* no sinal *Component.onCompleted {}*, e estará pronto para uso: 76 | 77 | ```qml 78 | Loader { 79 | id: loadCRM 80 | 81 | anchors.fill: parent 82 | source: "qrc:/modules/crm/StartCRM.qml" 83 | 84 | Component.onCompleted: { 85 | reloader.registerLoader(loadCRM); // este loader agora será monitorado 86 | } 87 | } 88 | ``` 89 | 90 | Copyright (c) 2020 Arthur Turrini. 91 | 92 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turrini/QMLreLoader/6bd4d46ce2e7358da607c61369b36a6f4680eef8/demo.gif -------------------------------------------------------------------------------- /example/components/Nested1.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | anchors.fill: parent 5 | color: "red" 6 | 7 | Text { 8 | anchors.top: parent.top 9 | anchors.horizontalCenter: parent.horizontalCenter 10 | text: "Nested1.qml datetime -> " + new Date() 11 | font.pixelSize: 14 12 | 13 | color: "white" 14 | } 15 | 16 | Loader { 17 | id: anotherLoader 18 | 19 | anchors.centerIn: parent 20 | 21 | width: 450 22 | height: 100 23 | 24 | source: "qrc:/components/Nested2.qml" 25 | 26 | Component.onCompleted: { 27 | reloader.registerLoader(anotherLoader); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/components/Nested2.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | anchors.fill: parent 5 | color: "green" 6 | 7 | Text { 8 | anchors.top: parent.top 9 | anchors.horizontalCenter: parent.horizontalCenter 10 | text: "Nested2.qml datetime -> " + new Date() 11 | font.pixelSize: 14 12 | 13 | color: "white" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/example.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | 3 | CONFIG += c++11 silent 4 | 5 | INCLUDEPATH += $$PWD 6 | 7 | SOURCES += \ 8 | $$PWD/main.cpp 9 | 10 | RESOURCES += \ 11 | $$PWD/qml.qrc 12 | 13 | # QML reLoader 14 | include($$PWD/../qmlreloader/qmlreloader.pri) 15 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /// Include the header 5 | #include "qmlreloader.h" 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 10 | 11 | QGuiApplication app(argc, argv); 12 | 13 | QQmlApplicationEngine engine; 14 | 15 | /// Register the engine within QML reLoader 16 | QMLreLoader::QMLreLoader reloader(&engine); 17 | 18 | const QUrl url(QStringLiteral("qrc:/main.qml")); 19 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 20 | &app, [url](QObject *obj, const QUrl &objUrl) { 21 | if (!obj && url == objUrl) 22 | QCoreApplication::exit(-1); 23 | }, Qt::QueuedConnection); 24 | engine.load(url); 25 | 26 | return app.exec(); 27 | } 28 | -------------------------------------------------------------------------------- /example/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Window 2.0 3 | 4 | Window { 5 | width: 640 6 | height: 360 7 | visible: true 8 | 9 | title: "QML reLoader Example" 10 | 11 | Text { 12 | anchors.top: parent.top 13 | anchors.horizontalCenter: parent.horizontalCenter 14 | text: "main.qml datetime -> " + new Date() 15 | font.pixelSize: 14 16 | } 17 | 18 | Loader { 19 | id: mainLoader 20 | 21 | anchors.centerIn: parent 22 | 23 | width: 500 24 | height: 200 25 | 26 | source: "qrc:/components/Nested1.qml" 27 | 28 | Component.onCompleted: { 29 | reloader.registerLoader(mainLoader); 30 | } 31 | } // loader 32 | } 33 | -------------------------------------------------------------------------------- /example/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | components/Nested1.qml 7 | components/Nested2.qml 8 | 9 | 10 | -------------------------------------------------------------------------------- /qmlreloader/filewatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "filewatcher.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #define WAIT_FOR_CHANGES_TIMEOUT 120 12 | 13 | namespace QMLreLoader { 14 | 15 | class FileWatcherPrivate 16 | { 17 | public: 18 | FileWatcherPrivate() 19 | : fsWatcher(nullptr) 20 | , directoryChangesTimeout(nullptr) 21 | , fileChangesTimeout(nullptr) 22 | { 23 | } 24 | 25 | ~FileWatcherPrivate() 26 | { 27 | if(fsWatcher) { 28 | delete fsWatcher; 29 | fsWatcher = nullptr; 30 | } 31 | 32 | if(directoryChangesTimeout) { 33 | delete directoryChangesTimeout; 34 | directoryChangesTimeout = nullptr; 35 | } 36 | 37 | if(fileChangesTimeout) { 38 | delete fileChangesTimeout; 39 | fileChangesTimeout = nullptr; 40 | } 41 | } 42 | 43 | QString watchDirectory; 44 | 45 | QFileSystemWatcher *fsWatcher; 46 | QTimer *directoryChangesTimeout, *fileChangesTimeout; 47 | 48 | QStringList changedDirs, changedFiles; 49 | void scanDirectory(const QString &directory); 50 | }; 51 | 52 | FileWatcher::FileWatcher(QObject *parent) 53 | : QObject(parent) 54 | , d(new FileWatcherPrivate()) 55 | { 56 | d->fsWatcher = new QFileSystemWatcher(this); 57 | 58 | connect(d->fsWatcher, &QFileSystemWatcher::directoryChanged, this, [&](const QString &dirPath){ 59 | if(!d->changedDirs.contains(dirPath)) 60 | d->changedDirs.append(dirPath); 61 | d->directoryChangesTimeout->start(); 62 | }); 63 | 64 | connect(d->fsWatcher, &QFileSystemWatcher::fileChanged, this, [&](const QString &filePath){ 65 | if(!d->changedFiles.contains(filePath)) 66 | d->changedFiles.append(filePath); 67 | d->fileChangesTimeout->start(); 68 | }); 69 | 70 | /// Changed Directories (rescan) 71 | d->directoryChangesTimeout = new QTimer(this); 72 | connect(d->directoryChangesTimeout, &QTimer::timeout, this, [&]() { 73 | d->changedDirs.sort(); 74 | for(auto &directory : d->changedDirs) 75 | d->scanDirectory(directory); 76 | }); 77 | d->directoryChangesTimeout->setInterval(WAIT_FOR_CHANGES_TIMEOUT); 78 | d->directoryChangesTimeout->setSingleShot(true); 79 | 80 | /// Changed Files 81 | d->fileChangesTimeout = new QTimer(this); 82 | connect(d->fileChangesTimeout, &QTimer::timeout, this, [&]() { 83 | d->changedFiles.sort(); 84 | emit changedFiles(d->changedFiles); 85 | d->changedFiles.clear(); 86 | }); 87 | d->fileChangesTimeout->setInterval(WAIT_FOR_CHANGES_TIMEOUT); 88 | d->fileChangesTimeout->setSingleShot(true); 89 | } 90 | 91 | FileWatcher::~FileWatcher() 92 | { 93 | delete d; 94 | d = nullptr; 95 | } 96 | 97 | QString FileWatcher::watchDirectory() const 98 | { 99 | return d->watchDirectory; 100 | } 101 | 102 | void FileWatcher::setWatchDirectory(const QString &watchDirectory) 103 | { 104 | d->watchDirectory = watchDirectory; 105 | if(!d->fsWatcher->files().isEmpty()) 106 | d->fsWatcher->removePaths(d->fsWatcher->files()); 107 | if(!d->fsWatcher->directories().isEmpty()) 108 | d->fsWatcher->removePaths(d->fsWatcher->directories()); 109 | d->scanDirectory(watchDirectory); 110 | } 111 | 112 | void FileWatcherPrivate::scanDirectory(const QString &directory) 113 | { 114 | QString absolutePath = QDir(directory).absolutePath(); 115 | if(fsWatcher->directories().contains(absolutePath)) { 116 | if(!QDir(absolutePath).exists()) { 117 | fsWatcher->removePath(absolutePath); 118 | return; 119 | } 120 | } 121 | 122 | static QStringList fileTypes = QStringList() << "*.qml" << "*.js" << "*.json" << "*.xml" << "*.txt"; 123 | fsWatcher->addPath(QDir(absolutePath).absolutePath()); 124 | QDirIterator i(directory, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories); 125 | while(i.hasNext()) { 126 | QString x = i.next(); 127 | fsWatcher->addPath(QDir(x).absolutePath()); 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /qmlreloader/filewatcher.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEWATCHER_H 2 | #define FILEWATCHER_H 3 | 4 | #include 5 | #include 6 | 7 | namespace QMLreLoader { 8 | 9 | class FileWatcherPrivate; 10 | 11 | class FileWatcher : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit FileWatcher(QObject *parent = nullptr); 17 | ~FileWatcher(); 18 | 19 | QString watchDirectory() const; 20 | void setWatchDirectory(const QString &watchDirectory); 21 | 22 | signals: 23 | void changedFiles(const QStringList &changedFiles); 24 | 25 | private: 26 | FileWatcherPrivate *d; 27 | }; 28 | 29 | } 30 | 31 | #endif // FILEWATCHER_H 32 | -------------------------------------------------------------------------------- /qmlreloader/qmlfileinterceptor.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlfileinterceptor.h" 2 | 3 | namespace QMLreLoader { 4 | 5 | QUrl QMLFileInterceptor::intercept(const QUrl &path, QQmlAbstractUrlInterceptor::DataType type) 6 | { 7 | Q_UNUSED(type) 8 | 9 | if(Q_LIKELY(path.scheme() == QStringLiteral("qrc"))) { 10 | QString _path = path.toString(); 11 | 12 | if(_path.startsWith(QStringLiteral("qrc:/qt-project.org"))) 13 | return path; 14 | 15 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 16 | if(type == QQmlAbstractUrlInterceptor::QmldirFile) 17 | return path; 18 | #endif 19 | _path.replace(QStringLiteral("qrc:/"), sourcePath); 20 | 21 | return QUrl::fromUserInput(_path); 22 | } 23 | return path; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /qmlreloader/qmlfileinterceptor.h: -------------------------------------------------------------------------------- 1 | #ifndef QMLFILEINTERCEPTOR_H 2 | #define QMLFILEINTERCEPTOR_H 3 | 4 | #include 5 | 6 | namespace QMLreLoader { 7 | 8 | class QMLFileInterceptor : public QQmlAbstractUrlInterceptor 9 | { 10 | public: 11 | QMLFileInterceptor() { } 12 | QMLFileInterceptor(const QString &sourcePath) : sourcePath(sourcePath) { } 13 | virtual ~QMLFileInterceptor() { } 14 | 15 | void setSourcePath(const QString &sourcePath) { this->sourcePath = sourcePath; } 16 | 17 | QUrl intercept(const QUrl &path, DataType type); 18 | private: 19 | QString sourcePath; 20 | }; 21 | 22 | } 23 | 24 | #endif // QMLFILEINTERCEPTOR_H 25 | -------------------------------------------------------------------------------- /qmlreloader/qmlreloader.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlreloader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "qmlfileinterceptor.h" 19 | #include "filewatcher.h" 20 | 21 | namespace QMLreLoader { 22 | 23 | class QMLreLoaderPrivate 24 | { 25 | public: 26 | QMLreLoaderPrivate(QQmlEngine *engine, const QString &watchDirectory) 27 | : engine(engine) 28 | , watchDirectory(watchDirectory) 29 | , fileInterceptor(new QMLFileInterceptor(watchDirectory)) 30 | , fileWatcher(nullptr) 31 | { 32 | } 33 | 34 | ~QMLreLoaderPrivate() 35 | { 36 | if(fileWatcher) { 37 | delete fileWatcher; 38 | fileWatcher = nullptr; 39 | } 40 | } 41 | 42 | QQmlEngine *engine; 43 | QStack registeredLoaders; 44 | 45 | QString watchDirectory; 46 | QMLFileInterceptor *fileInterceptor; 47 | FileWatcher *fileWatcher; 48 | }; 49 | 50 | QMLreLoader::QMLreLoader(QQmlEngine *engine, const QString &watchDirectory, QObject *parent) 51 | : QObject(parent) 52 | , d(new QMLreLoaderPrivate(engine, watchDirectory)) 53 | { 54 | if(!engine) { 55 | qDebug() << __PRETTY_FUNCTION__ << ": You must inform a QQmlEngine."; 56 | return; 57 | } 58 | engine->setUrlInterceptor(d->fileInterceptor); 59 | engine->rootContext()->setContextProperty("reloader", this); 60 | } 61 | 62 | QMLreLoader::~QMLreLoader() 63 | { 64 | delete d; 65 | } 66 | 67 | QString QMLreLoader::watchDirectory() const 68 | { 69 | return d->watchDirectory; 70 | } 71 | 72 | void QMLreLoader::setWatchDirectory(const QString &watchDirectory) 73 | { 74 | d->watchDirectory = watchDirectory; 75 | d->fileInterceptor->setSourcePath(watchDirectory); 76 | } 77 | 78 | void QMLreLoader::start() 79 | { 80 | if(d->fileWatcher == nullptr) { 81 | d->fileWatcher = new FileWatcher(this); 82 | d->fileWatcher->setWatchDirectory(d->watchDirectory); 83 | connect(d->fileWatcher, &FileWatcher::changedFiles, this, [this](const QStringList &changes){ 84 | applyQmlChanges(changes); 85 | }); 86 | } 87 | } 88 | 89 | void QMLreLoader::stop() 90 | { 91 | if(d->fileWatcher) { 92 | delete d->fileWatcher; 93 | d->fileWatcher = nullptr; 94 | } 95 | } 96 | 97 | void QMLreLoader::registerLoader(QObject *loader) 98 | { 99 | if(!d->engine) { 100 | qDebug() << __PRETTY_FUNCTION__ << ": Not ready: missing QQmlEngine."; 101 | return; 102 | } 103 | 104 | if(!loader) { 105 | qDebug() << __PRETTY_FUNCTION__ << ": Invalid object."; 106 | return; 107 | } 108 | 109 | if(d->watchDirectory.isEmpty()) { 110 | return; 111 | } 112 | 113 | bool notALoader = !QString(loader->metaObject()->className()).startsWith("QQuickLoader"); 114 | if(notALoader) { 115 | qDebug() << __PRETTY_FUNCTION__ << ": Not a Loader." << loader; 116 | return; 117 | } 118 | 119 | if(!d->registeredLoaders.contains(loader)) 120 | d->registeredLoaders.push(loader); 121 | else 122 | return; 123 | 124 | connect(loader, &QObject::destroyed, this, [&](){ 125 | d->registeredLoaders.removeAll(loader); 126 | }); 127 | 128 | if(d->fileWatcher == nullptr) { 129 | d->fileWatcher = new FileWatcher(this); 130 | d->fileWatcher->setWatchDirectory(d->watchDirectory); 131 | connect(d->fileWatcher, &FileWatcher::changedFiles, this, [this](const QStringList &changes){ 132 | applyQmlChanges(changes); 133 | }); 134 | } 135 | } 136 | 137 | void QMLreLoader::unregisterLoader(QObject *loader) 138 | { 139 | d->registeredLoaders.removeAll(loader); 140 | } 141 | 142 | void QMLreLoader::applyQmlChanges(const QStringList &fileChanges) 143 | { 144 | if(d->registeredLoaders.count() == 0 || fileChanges.isEmpty()) 145 | return; 146 | 147 | QObject *destLoader = d->registeredLoaders.first(); 148 | 149 | for(auto &loader : d->registeredLoaders) { 150 | QObjectList parents, children; 151 | parents << loader; 152 | 153 | do { 154 | children.clear(); 155 | 156 | for(auto &parent : parents) { 157 | if(!parent) continue; 158 | 159 | for(auto &child : parent->findChildren(QString(), Qt::FindDirectChildrenOnly)) { 160 | if(!d->registeredLoaders.contains(child)) 161 | children << child; 162 | } 163 | 164 | static QStringList qmlproperties = QStringList() << "item" << "contentItem" << "delegate"; 165 | for(auto &qmlproperty : qmlproperties) { 166 | QObject *item = qvariant_cast(QQmlProperty::read(parent, qmlproperty)); 167 | if(!d->registeredLoaders.contains(item)) { 168 | for(auto &child : item->findChildren(QString(), Qt::FindDirectChildrenOnly)) { 169 | if(!d->registeredLoaders.contains(child)) 170 | children << child; 171 | } 172 | } 173 | } 174 | } 175 | 176 | for(auto &child : children) { 177 | if(!child) 178 | continue; 179 | 180 | QQmlData *data = QQmlData::get(child, false); 181 | if(data && data->context) { 182 | QString qmlFile = data->context->url().toString(); 183 | if(qmlFile.contains('/')) 184 | qmlFile = qmlFile.mid(qmlFile.lastIndexOf('/') + 1); 185 | 186 | for(auto changed : fileChanges) { 187 | if(changed.contains('/')) 188 | changed = changed.mid(changed.lastIndexOf('/') + 1); 189 | 190 | if(qmlFile == changed) { 191 | //qDebug() << __PRETTY_FUNCTION__ << ": found" << qmlFile << "as the most near direct child of Loader" << loader; 192 | destLoader = loader; 193 | goto doQMLReload; 194 | } 195 | } // for changed 196 | } // qmldata 197 | } // for children 198 | parents = children; 199 | } while(children.count() > 0); 200 | } 201 | 202 | doQMLReload: 203 | QString sourcePath = QQmlProperty::read(destLoader, "source").toString(); 204 | //qDebug() << __PRETTY_FUNCTION__ << ": Re-applying source property on" << destLoader << "with" << sourcePath; 205 | QQmlProperty::write(destLoader, "source", QString()); 206 | QQuickPixmap::purgeCache(); 207 | d->engine->clearComponentCache(); 208 | QQmlProperty::write(destLoader, "source", sourcePath); 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /qmlreloader/qmlreloader.h: -------------------------------------------------------------------------------- 1 | #ifndef QMLRELOADER_H 2 | #define QMLRELOADER_H 3 | 4 | #include 5 | 6 | class QQmlEngine; 7 | class QQuickItem; 8 | 9 | namespace QMLreLoader { 10 | 11 | class QMLreLoaderPrivate; 12 | 13 | class QMLreLoader : public QObject 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit QMLreLoader(QQmlEngine *engine 19 | , const QString &watchDirectory = PROJECT_SOURCE_DIR 20 | , QObject *parent = nullptr); 21 | ~QMLreLoader(); 22 | 23 | QString watchDirectory() const; 24 | void setWatchDirectory(const QString &watchDirectory); 25 | 26 | public slots: 27 | void start(); 28 | void stop(); 29 | 30 | void registerLoader(QObject *loader); 31 | void unregisterLoader(QObject *loader); 32 | 33 | private: 34 | QMLreLoaderPrivate *d; 35 | void applyQmlChanges(const QStringList &fileChanges); 36 | }; 37 | 38 | } 39 | 40 | #endif // QMLRELOADER_H 41 | -------------------------------------------------------------------------------- /qmlreloader/qmlreloader.pri: -------------------------------------------------------------------------------- 1 | QT += quick quick-private 2 | 3 | CONFIG += c++11 silent 4 | 5 | DEFINES += PROJECT_SOURCE_DIR=\\\"$$_PRO_FILE_PWD_/\\\" 6 | 7 | INCLUDEPATH += $$PWD 8 | 9 | SOURCES += \ 10 | $$PWD/filewatcher.cpp \ 11 | $$PWD/qmlfileinterceptor.cpp \ 12 | $$PWD/qmlreloader.cpp 13 | 14 | HEADERS += \ 15 | $$PWD/filewatcher.h \ 16 | $$PWD/qmlfileinterceptor.h \ 17 | $$PWD/qmlreloader.h 18 | --------------------------------------------------------------------------------