[Dev] Added QML reloading.
This commit is contained in:
parent
9a119cb694
commit
f3b3fc85ac
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,3 +11,4 @@ AppDir
|
||||||
Makefile
|
Makefile
|
||||||
linuxdeploy*
|
linuxdeploy*
|
||||||
other_libs
|
other_libs
|
||||||
|
sffmpeg
|
37
CMakeLists.txt
Normal file
37
CMakeLists.txt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
cmake_minimum_required(VERSION 3.1.0)
|
||||||
|
|
||||||
|
project(KittehPlayer)
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -fstrict-aliasing -Wno-deprecated-declarations -Wno-unused-variable")
|
||||||
|
|
||||||
|
option(DEVELOP "Enable runtime QML reloading for developing." OFF)
|
||||||
|
|
||||||
|
|
||||||
|
find_package(Qt5Core REQUIRED)
|
||||||
|
find_package(Qt5 REQUIRED Qml Quick Gui Widgets Core)
|
||||||
|
|
||||||
|
find_package(Qt5QuickCompiler)
|
||||||
|
qtquick_compiler_add_resources(qml_QRC src/qml/qml.qrc)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/main.cpp
|
||||||
|
src/mpvobject.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DEVELOP)
|
||||||
|
set(SOURCES ${SOURCES} runtimeqml/runtimeqml.cpp)
|
||||||
|
add_definitions(-DQRC_SOURCE_PATH="${PROJECT_SOURCE_DIR}/src/qml")
|
||||||
|
endif(DEVELOP)
|
||||||
|
|
||||||
|
add_executable(KittehPlayer ${SOURCES} ${qml_QRC})
|
||||||
|
|
||||||
|
# Use the Qml/Quick modules from Qt 5.
|
||||||
|
target_link_libraries(KittehPlayer mpv)
|
||||||
|
|
||||||
|
qt5_use_modules(KittehPlayer Qml Quick Core Gui Widgets)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
75
runtimeqml/.gitignore
vendored
Normal file
75
runtimeqml/.gitignore
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# This file is used to ignore files which are generated
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.autosave
|
||||||
|
*.a
|
||||||
|
*.core
|
||||||
|
*.moc
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*_pch.h.cpp
|
||||||
|
*_resource.rc
|
||||||
|
*.qm
|
||||||
|
.#*
|
||||||
|
*.*#
|
||||||
|
core
|
||||||
|
!core/
|
||||||
|
tags
|
||||||
|
.DS_Store
|
||||||
|
.directory
|
||||||
|
*.debug
|
||||||
|
Makefile*
|
||||||
|
*.prl
|
||||||
|
*.app
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Thumbs.db
|
||||||
|
*.res
|
||||||
|
*.rc
|
||||||
|
/.qmake.cache
|
||||||
|
/.qmake.stash
|
||||||
|
|
||||||
|
# qtcreator generated files
|
||||||
|
*.pro.user*
|
||||||
|
# qtcreator builds of test project
|
||||||
|
build-RuntimeQML-*
|
||||||
|
|
||||||
|
# xemacs temporary files
|
||||||
|
*.flc
|
||||||
|
|
||||||
|
# Vim temporary files
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# Visual Studio generated files
|
||||||
|
*.ib_pdb_index
|
||||||
|
*.idb
|
||||||
|
*.ilk
|
||||||
|
*.pdb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.vcproj
|
||||||
|
*vcproj.*.*.user
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.vcxproj
|
||||||
|
*vcxproj.*
|
||||||
|
|
||||||
|
# MinGW generated files
|
||||||
|
*.Debug
|
||||||
|
*.Release
|
||||||
|
|
||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
# --------
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
24
runtimeqml/LICENSE
Normal file
24
runtimeqml/LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2018, Benjamin Balga
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the <organization> nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
87
runtimeqml/README.md
Normal file
87
runtimeqml/README.md
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# Runtime QML for Qt
|
||||||
|
|
||||||
|
**Written by**: *Benjamin Balga.*
|
||||||
|
**Copyright**: ***2018***, *Benjamin Balga*, released under BSD license.
|
||||||
|
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This is a base project to get runtime QML reload in your Qt project.
|
||||||
|
It allows you to reload all QML code at runtime, without recompiling or restarting your app, saving time.
|
||||||
|
|
||||||
|
With auto-reload, QML files are watched (based on the QRC file) and reloaded when you save them, or can trigger it manually.
|
||||||
|
|
||||||
|
On reload, all windows are closed, and the main window is reloaded. All "QML-only data" is lost, so use links to C++ models/properties as needed.
|
||||||
|
|
||||||
|
It only works with Window component as top object, or QQuickWindow subclasses.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
Example project is located here: https://github.com/GIPdA/runtimeqml_examples
|
||||||
|
|
||||||
|
|
||||||
|
## How to use it in your project
|
||||||
|
|
||||||
|
Clone the repo into your project (or copy-paste the ```runtimeqml``` folder) and import the ```.pri``` project file into your ```.pro``` file:
|
||||||
|
|
||||||
|
include(runtimeqml/runtimeqml.pri)
|
||||||
|
|
||||||
|
|
||||||
|
### With Qbs
|
||||||
|
The Qbs project file includes RuntimeQML as a static library. Check the example project to see how to include it in your project.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Include ```runtimeqml.h``` header file, and create the RuntimeQML object (after the QML engine) :
|
||||||
|
|
||||||
|
RuntimeQML *rt = new RuntimeQML(&engine, QRC_SOURCE_PATH"/qml.qrc");
|
||||||
|
|
||||||
|
The second argument is the path to your qrc file listing all your QML files, needed for the auto-reload feature only. You can omit it if you don't want auto-reload.
|
||||||
|
```QRC_SOURCE_PATH``` is defined in the ```.pri/.qbs``` file to its parent path, just to not have to manually set an absolute path...
|
||||||
|
|
||||||
|
|
||||||
|
Set the "options" you want, or not:
|
||||||
|
|
||||||
|
rt->noDebug(); // Removes debug prints
|
||||||
|
rt->setAutoReload(true); // Enable auto-reload (begin to watch files)
|
||||||
|
//rt->setCloseAllOnReload(false); // Don't close all windows on reload. Not advised!
|
||||||
|
rt->setMainQmlFilename("main.qml"); // This is the file that loaded on reload, default is "main.qml"
|
||||||
|
|
||||||
|
For the auto-reload feature:
|
||||||
|
|
||||||
|
rt->addSuffix("conf"); // Adds a file suffix to the "white list" for watched files. "qml" is already in.
|
||||||
|
rt->ignorePrefix("/test"); // Ignore a prefix in the QRC file.
|
||||||
|
rt->ignoreFile("/Page2.qml"); // Ignore a file name with prefix (supports classic wildcard matching)
|
||||||
|
|
||||||
|
|
||||||
|
Then load the main QML file :
|
||||||
|
|
||||||
|
rt->reload();
|
||||||
|
|
||||||
|
And you're all set!
|
||||||
|
|
||||||
|
|
||||||
|
You can also check the test project. Beware, includes and defines differs a bit...
|
||||||
|
|
||||||
|
|
||||||
|
## Manual reload
|
||||||
|
|
||||||
|
Add the RuntimeQML object to the QML context:
|
||||||
|
|
||||||
|
engine.rootContext()->setContextProperty("RuntimeQML", rt);
|
||||||
|
|
||||||
|
Trigger the reload when and where you want, like with a button:
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Reload"
|
||||||
|
onClicked: {
|
||||||
|
RuntimeQML.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
You can do it in C++ too, of course.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
See LICENSE file.
|
398
runtimeqml/runtimeqml.cpp
Normal file
398
runtimeqml/runtimeqml.cpp
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
#include "runtimeqml.h"
|
||||||
|
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Construct a RuntimeQML object with a path to the qrc file.
|
||||||
|
* \param engine App engine to reload.
|
||||||
|
* \param qrcFilename File name of the QRC file for auto reload.
|
||||||
|
* \param parent Pointer to a parent object.
|
||||||
|
*/
|
||||||
|
RuntimeQML::RuntimeQML(QQmlApplicationEngine* engine, const QString &qrcFilename, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_engine(engine),
|
||||||
|
m_qrcFilename(qrcFilename),
|
||||||
|
m_mainQmlFilename("main.qml")
|
||||||
|
{
|
||||||
|
m_allowedSuffixList << "qml";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the absolute path for the given qml file.
|
||||||
|
* \param qmlFile Qml filename
|
||||||
|
*/
|
||||||
|
QString RuntimeQML::adjustPath(QString qmlFile)
|
||||||
|
{
|
||||||
|
return m_selector.select(qrcAbsolutePath() + "/" + qmlFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the absolute path to the QRC file.
|
||||||
|
*/
|
||||||
|
QString RuntimeQML::qrcAbsolutePath() const
|
||||||
|
{
|
||||||
|
return QFileInfo(m_qrcFilename).absolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Filename of the QRC file.
|
||||||
|
*/
|
||||||
|
QString RuntimeQML::qrcFilename() const
|
||||||
|
{
|
||||||
|
return m_qrcFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief If true, files are watched for changes and auto-reloaded.
|
||||||
|
* Otherwise, you need to trigger a reload manually from your code by calling reload().
|
||||||
|
* \sa reload
|
||||||
|
*/
|
||||||
|
bool RuntimeQML::autoReload() const
|
||||||
|
{
|
||||||
|
return m_autoReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief If true, all open windows will be closed upon reload.
|
||||||
|
* \default true
|
||||||
|
*/
|
||||||
|
bool RuntimeQML::closeAllOnReload() const
|
||||||
|
{
|
||||||
|
return m_closeAllOnReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief QRC prefixes that are ignored.
|
||||||
|
*/
|
||||||
|
const QList<QString>& RuntimeQML::prefixIgnoreList() const
|
||||||
|
{
|
||||||
|
return m_prefixIgnoreList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Files that are ignored.
|
||||||
|
*/
|
||||||
|
const QList<QString> &RuntimeQML::fileIgnoreList() const
|
||||||
|
{
|
||||||
|
return m_fileIgnoreList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Allowed suffixes to filter files to watch for changes.
|
||||||
|
* By default contains only "qml".
|
||||||
|
*/
|
||||||
|
const QList<QString> &RuntimeQML::allowedSuffixes() const
|
||||||
|
{
|
||||||
|
return m_allowedSuffixList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Call it if you don't want debug outputs from this class.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::noDebug()
|
||||||
|
{
|
||||||
|
if (m_noDebug)
|
||||||
|
return;
|
||||||
|
m_noDebug = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Reload the window.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::reload()
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "reloadQml", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Call it from QML to set the current QQuickWindow.
|
||||||
|
* You shouldn't need to call it as it is done automatically on reload.
|
||||||
|
* \param window
|
||||||
|
*/
|
||||||
|
void RuntimeQML::setWindow(QQuickWindow* window)
|
||||||
|
{
|
||||||
|
if (window == m_window)
|
||||||
|
return;
|
||||||
|
m_window = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Set the QRC filename for files to watch for changes.
|
||||||
|
* \param qrcFilename Path to a .qrc file.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::setQrcFilename(QString qrcFilename)
|
||||||
|
{
|
||||||
|
if (m_qrcFilename == qrcFilename)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_qrcFilename = qrcFilename;
|
||||||
|
emit qrcFilenameChanged(qrcFilename);
|
||||||
|
|
||||||
|
loadQrcFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Set the name of the main qml file.
|
||||||
|
* Default is "main.qml".
|
||||||
|
* \param filename The main qml filename.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::setMainQmlFilename(QString filename)
|
||||||
|
{
|
||||||
|
if (m_mainQmlFilename == filename)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_mainQmlFilename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief If true, files are watched for changes and auto-reloaded.
|
||||||
|
* Otherwise, you need to trigger a reload manually from your code by calling reload().
|
||||||
|
* \param reload True to auto-reload, false otherwise.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::setAutoReload(bool autoReload)
|
||||||
|
{
|
||||||
|
if (m_autoReload == autoReload)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_autoReload = autoReload;
|
||||||
|
emit autoReloadChanged(autoReload);
|
||||||
|
|
||||||
|
if (autoReload)
|
||||||
|
loadQrcFiles();
|
||||||
|
else
|
||||||
|
unloadFileWatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief If true, all open windows are closed upon reload. Otherwise, might cause "link" errors with QML components.
|
||||||
|
* \param closeAllOnReload True to close all windows on reload, false otherwise.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::setCloseAllOnReload(bool closeAllOnReload)
|
||||||
|
{
|
||||||
|
if (m_closeAllOnReload == closeAllOnReload)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_closeAllOnReload = closeAllOnReload;
|
||||||
|
emit closeAllOnReloadChanged(m_closeAllOnReload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Add a QRC prefix to ignore.
|
||||||
|
* \note Relevant for auto-reload only.
|
||||||
|
* \param prefix Prefix to ignore.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::ignoreQrcPrefix(const QString& prefix)
|
||||||
|
{
|
||||||
|
if (m_prefixIgnoreList.contains(prefix))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_prefixIgnoreList.append(prefix);
|
||||||
|
|
||||||
|
if (m_autoReload)
|
||||||
|
loadQrcFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Add a filename to ignore from changes.
|
||||||
|
* Applies to the full filename in the QRC entry (i.e. the local "path"), with the prefix.
|
||||||
|
* Supports "file globbing" matching using wildcards.
|
||||||
|
* \note Relevant for auto-reload only.
|
||||||
|
* \param filename Filename to ignore.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::ignoreFile(const QString &filename)
|
||||||
|
{
|
||||||
|
if (m_fileIgnoreList.contains(filename))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_fileIgnoreList.append(filename);
|
||||||
|
|
||||||
|
if (m_autoReload)
|
||||||
|
loadQrcFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Allow a file suffix to be watched for changes.
|
||||||
|
* \note Relevant for auto-reload only.
|
||||||
|
* \param suffix
|
||||||
|
*/
|
||||||
|
void RuntimeQML::addSuffix(const QString &suffix)
|
||||||
|
{
|
||||||
|
if (m_allowedSuffixList.contains(suffix))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_allowedSuffixList.append(suffix);
|
||||||
|
|
||||||
|
if (m_autoReload)
|
||||||
|
loadQrcFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Reload the QML. Do not call it directly, use reload() instead.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::reloadQml()
|
||||||
|
{
|
||||||
|
if (m_mainQmlFilename.isEmpty()) {
|
||||||
|
qWarning("No QML file specified.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_window) {
|
||||||
|
if (m_closeAllOnReload) {
|
||||||
|
// Find all child windows and close them
|
||||||
|
auto const allWindows = m_window->findChildren<QQuickWindow*>();
|
||||||
|
for (int i {0}; i < allWindows.size(); ++i) {
|
||||||
|
QQuickWindow* w = qobject_cast<QQuickWindow*>(allWindows.at(i));
|
||||||
|
if (w) {
|
||||||
|
w->close();
|
||||||
|
w->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_window->close();
|
||||||
|
m_window->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_engine->clearComponentCache();
|
||||||
|
// TODO: test with network files
|
||||||
|
// TODO: QString path to QUrl doesn't work under Windows with load() (load fail)
|
||||||
|
m_engine->load(m_selector.select(qrcAbsolutePath() + "/" + m_mainQmlFilename));
|
||||||
|
// NOTE: QQmlApplicationEngine::rootObjects() isn't cleared, should it be?
|
||||||
|
|
||||||
|
if (!m_engine->rootObjects().isEmpty()) {
|
||||||
|
QQuickWindow* w = qobject_cast<QQuickWindow*>(m_engine->rootObjects().last());
|
||||||
|
if (w) m_window = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for (auto *o : m_engine->rootObjects()) {
|
||||||
|
// qDebug() << "> " << o;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Called when a watched file changed, from QFileSystemWatcher.
|
||||||
|
* \param path Path/file that triggered the signal.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::fileChanged(const QString& path)
|
||||||
|
{
|
||||||
|
if (!m_noDebug)
|
||||||
|
qDebug() << "Reloading qml:" << path;
|
||||||
|
reload();
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
// Deleted files are removed from the watcher, re-add the file for
|
||||||
|
// systems that delete files to update them
|
||||||
|
if (m_fileWatcher)
|
||||||
|
QTimer::singleShot(500, m_fileWatcher, [this,path](){
|
||||||
|
m_fileWatcher->addPath(path);
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Load qml from the QRC file to watch them.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::loadQrcFiles()
|
||||||
|
{
|
||||||
|
unloadFileWatcher();
|
||||||
|
|
||||||
|
m_fileWatcher = new QFileSystemWatcher(this);
|
||||||
|
connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &RuntimeQML::fileChanged);
|
||||||
|
|
||||||
|
QFile file(m_qrcFilename);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
qWarning("Unable to open resource file '%s', RuntimeQML will not work! Error: %s",
|
||||||
|
qPrintable(m_qrcFilename), qPrintable(file.errorString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString const basePath = qrcAbsolutePath() + "/";
|
||||||
|
QString currentPrefix;
|
||||||
|
|
||||||
|
// Read each entry
|
||||||
|
QXmlStreamReader inputStream(&file);
|
||||||
|
while (!inputStream.atEnd() && !inputStream.hasError()) {
|
||||||
|
inputStream.readNext();
|
||||||
|
if (inputStream.isStartElement()) {
|
||||||
|
QString name { inputStream.name().toString() };
|
||||||
|
|
||||||
|
// Check prefix
|
||||||
|
if (name == "qresource") {
|
||||||
|
if (inputStream.attributes().hasAttribute("prefix")) {
|
||||||
|
auto p = inputStream.attributes().value("prefix").toString();
|
||||||
|
if (m_prefixIgnoreList.contains(p)) {
|
||||||
|
// Ignore this prefix, loop through elements in this 'qresource' tag
|
||||||
|
while (!inputStream.atEnd() && !inputStream.hasError()) {
|
||||||
|
inputStream.readNext();
|
||||||
|
if (inputStream.isEndElement() && inputStream.name() == "qresource")
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentPrefix = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file name
|
||||||
|
if (name == "file") {
|
||||||
|
QString const filename { inputStream.readElementText() };
|
||||||
|
|
||||||
|
// Check ignore list
|
||||||
|
QString const fullFilename { currentPrefix + filename };
|
||||||
|
auto it = std::find_if(m_fileIgnoreList.cbegin(), m_fileIgnoreList.cend(), [&](QString const& pattern) {
|
||||||
|
QRegExp re(pattern);
|
||||||
|
re.setPatternSyntax(QRegExp::WildcardUnix);
|
||||||
|
return re.exactMatch(fullFilename);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it != m_fileIgnoreList.cend())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QFileInfo const file { filename };
|
||||||
|
|
||||||
|
// Add to the watch list if the file suffix is allowed
|
||||||
|
if (m_allowedSuffixList.contains(file.suffix())) {
|
||||||
|
QString fp { m_selector.select(basePath + filename) };
|
||||||
|
m_fileWatcher->addPath(fp);
|
||||||
|
//qDebug() << " " << file.absoluteFilePath() << fp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_noDebug) {
|
||||||
|
qDebug("Watching QML files:");
|
||||||
|
int const fileCount = m_fileWatcher->files().size();
|
||||||
|
|
||||||
|
for (auto &f : m_fileWatcher->files()) {
|
||||||
|
qDebug() << " " << f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileCount > 0)
|
||||||
|
qDebug(" Total: %d", fileCount);
|
||||||
|
else
|
||||||
|
qDebug(" None.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Unload the file watcher.
|
||||||
|
*/
|
||||||
|
void RuntimeQML::unloadFileWatcher()
|
||||||
|
{
|
||||||
|
if (m_fileWatcher) {
|
||||||
|
disconnect(m_fileWatcher);
|
||||||
|
delete m_fileWatcher;
|
||||||
|
m_fileWatcher = nullptr;
|
||||||
|
}
|
||||||
|
}
|
82
runtimeqml/runtimeqml.h
Normal file
82
runtimeqml/runtimeqml.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#ifndef RUNTIMEQML_H
|
||||||
|
#define RUNTIMEQML_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QFileSelector>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeQML : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString qrcFilename READ qrcFilename WRITE setQrcFilename NOTIFY qrcFilenameChanged)
|
||||||
|
Q_PROPERTY(bool autoReload READ autoReload WRITE setAutoReload NOTIFY autoReloadChanged)
|
||||||
|
Q_PROPERTY(bool closeAllOnReload READ closeAllOnReload WRITE setCloseAllOnReload NOTIFY closeAllOnReloadChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RuntimeQML(QQmlApplicationEngine *engine, QString const& qrcFilename = QString(), QObject *parent = 0);
|
||||||
|
|
||||||
|
// If using QQmlFileSelector with Loader
|
||||||
|
Q_INVOKABLE QString adjustPath(QString qmlFile);
|
||||||
|
Q_INVOKABLE QString qrcAbsolutePath() const;
|
||||||
|
|
||||||
|
QString qrcFilename() const;
|
||||||
|
|
||||||
|
bool autoReload() const;
|
||||||
|
bool closeAllOnReload() const;
|
||||||
|
|
||||||
|
QList<QString> const & prefixIgnoreList() const;
|
||||||
|
QList<QString> const & fileIgnoreList() const;
|
||||||
|
QList<QString> const & allowedSuffixes() const;
|
||||||
|
|
||||||
|
void noDebug();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void autoReloadChanged(bool autoReload);
|
||||||
|
void qrcFilenameChanged(QString qrcFilename);
|
||||||
|
void closeAllOnReloadChanged(bool closeAllOnReload);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void reload();
|
||||||
|
void setWindow(QQuickWindow* window);
|
||||||
|
|
||||||
|
void setQrcFilename(QString qrcFilename);
|
||||||
|
void setMainQmlFilename(QString filename);
|
||||||
|
|
||||||
|
void setAutoReload(bool autoReload);
|
||||||
|
void setCloseAllOnReload(bool closeAllOnReload);
|
||||||
|
|
||||||
|
void ignoreQrcPrefix(QString const& prefix);
|
||||||
|
void ignoreFile(QString const& filename);
|
||||||
|
|
||||||
|
void addSuffix(QString const& suffix);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void reloadQml();
|
||||||
|
void fileChanged(const QString &path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loadQrcFiles();
|
||||||
|
void unloadFileWatcher();
|
||||||
|
|
||||||
|
QQmlApplicationEngine *m_engine {nullptr};
|
||||||
|
QQuickWindow *m_window {nullptr};
|
||||||
|
QFileSelector m_selector;
|
||||||
|
|
||||||
|
QString m_qrcFilename;
|
||||||
|
QString m_mainQmlFilename;
|
||||||
|
|
||||||
|
bool m_autoReload {false};
|
||||||
|
QFileSystemWatcher* m_fileWatcher {nullptr};
|
||||||
|
QList<QString> m_prefixIgnoreList;
|
||||||
|
QList<QString> m_fileIgnoreList;
|
||||||
|
QList<QString> m_allowedSuffixList;
|
||||||
|
bool m_noDebug {false};
|
||||||
|
bool m_closeAllOnReload {true};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RUNTIMEQML_H
|
13
runtimeqml/runtimeqml.pri
Normal file
13
runtimeqml/runtimeqml.pri
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Qt Quick Runtime Reloader
|
||||||
|
|
||||||
|
QT += core qml quick
|
||||||
|
|
||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
|
||||||
|
DEFINES += "QRC_SOURCE_PATH=\\\"$$PWD/..\\\""
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/runtimeqml.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
$$PWD/runtimeqml.h
|
18
runtimeqml/runtimeqml.qbs
Normal file
18
runtimeqml/runtimeqml.qbs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import qbs 1.0
|
||||||
|
|
||||||
|
StaticLibrary {
|
||||||
|
name: "runtimeqml"
|
||||||
|
files: [
|
||||||
|
"runtimeqml.cpp",
|
||||||
|
"runtimeqml.h",
|
||||||
|
]
|
||||||
|
Depends { name: 'cpp' }
|
||||||
|
Depends { name: "Qt.core" }
|
||||||
|
Depends { name: "Qt.quick" }
|
||||||
|
|
||||||
|
Export {
|
||||||
|
Depends { name: "cpp" }
|
||||||
|
cpp.includePaths: [product.sourceDirectory]
|
||||||
|
cpp.defines: ['QRC_SOURCE_PATH="'+path+'/.."']
|
||||||
|
}
|
||||||
|
}
|
15
src/main.cpp
15
src/main.cpp
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "mpvobject.h"
|
#include "mpvobject.h"
|
||||||
|
#ifdef QRC_SOURCE_PATH
|
||||||
|
#include "runtimeqml/runtimeqml.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
|
@ -59,6 +61,15 @@ view->setSource(QUrl("qrc:///player/main.qml"));
|
||||||
view->show();*/
|
view->show();*/
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
engine.load(QUrl(QStringLiteral("qrc:///player/main.qml")));
|
#ifdef QRC_SOURCE_PATH
|
||||||
|
RuntimeQML *rt = new RuntimeQML(&engine, QRC_SOURCE_PATH"/qml.qrc");
|
||||||
|
|
||||||
|
rt->setAutoReload(true);
|
||||||
|
rt->setMainQmlFilename("main.qml");
|
||||||
|
rt->reload();
|
||||||
|
#else
|
||||||
|
engine.load(QUrl(QStringLiteral("qrc:///player/main.qml")));
|
||||||
|
#endif
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ MpvObject::MpvObject(QQuickItem * parent)
|
||||||
throw std::runtime_error("could not create mpv context");
|
throw std::runtime_error("could not create mpv context");
|
||||||
|
|
||||||
mpv_set_option_string(mpv, "terminal", "yes");
|
mpv_set_option_string(mpv, "terminal", "yes");
|
||||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
//mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||||
|
|
||||||
// Fix?
|
// Fix?
|
||||||
mpv_set_option_string(mpv, "ytdl", "yes");
|
mpv_set_option_string(mpv, "ytdl", "yes");
|
||||||
|
|
BIN
src/qml/CustomMenuItem.qmlc
Normal file
BIN
src/qml/CustomMenuItem.qmlc
Normal file
Binary file not shown.
BIN
src/qml/codes.jsc
Normal file
BIN
src/qml/codes.jsc
Normal file
Binary file not shown.
|
@ -43,7 +43,6 @@ ApplicationWindow {
|
||||||
var sid = player.getProperty("sid")
|
var sid = player.getProperty("sid")
|
||||||
var vid = player.getProperty("vid")
|
var vid = player.getProperty("vid")
|
||||||
|
|
||||||
console.log("Updating Track Menu, Total Tracks: " + tracks)
|
|
||||||
for (track = 0; track <= tracks; track++) {
|
for (track = 0; track <= tracks; track++) {
|
||||||
var trackID = player.getProperty("track-list/" + track + "/id")
|
var trackID = player.getProperty("track-list/" + track + "/id")
|
||||||
var trackType = player.getProperty("track-list/" + track + "/type")
|
var trackType = player.getProperty("track-list/" + track + "/type")
|
||||||
|
@ -155,12 +154,10 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
function skipToNinth(val) {
|
function skipToNinth(val) {
|
||||||
console.log(val)
|
|
||||||
var skipto = 0
|
var skipto = 0
|
||||||
if (val != 0) {
|
if (val != 0) {
|
||||||
skipto = Math.floor(progressBar.to / 9 * val)
|
skipto = Math.floor(progressBar.to / 9 * val)
|
||||||
}
|
}
|
||||||
console.log(skipto)
|
|
||||||
player.command(["seek", skipto, "absolute"])
|
player.command(["seek", skipto, "absolute"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +172,6 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateVolume(volume) {
|
function updateVolume(volume) {
|
||||||
console.log(volume)
|
|
||||||
var muted = player.getProperty("mute")
|
var muted = player.getProperty("mute")
|
||||||
|
|
||||||
if (muted || volume === 0) {
|
if (muted || volume === 0) {
|
||||||
|
@ -405,7 +401,7 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
implicitWidth: parent.width
|
width: parent.width
|
||||||
implicitHeight: 10
|
implicitHeight: 10
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
|
@ -468,7 +464,7 @@ ApplicationWindow {
|
||||||
Menu {
|
Menu {
|
||||||
id: playbackMenuBarItem
|
id: playbackMenuBarItem
|
||||||
title: "Playback"
|
title: "Playback"
|
||||||
width: 100
|
width: 150
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
implicitHeight: 10
|
implicitHeight: 10
|
||||||
|
@ -476,7 +472,7 @@ ApplicationWindow {
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
}
|
}
|
||||||
delegate: CustomMenuItem {
|
delegate: CustomMenuItem {
|
||||||
width: 100
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
Action {
|
Action {
|
||||||
|
@ -569,7 +565,7 @@ ApplicationWindow {
|
||||||
Menu {
|
Menu {
|
||||||
id: tracksMenuBarItem
|
id: tracksMenuBarItem
|
||||||
title: "Tracks"
|
title: "Tracks"
|
||||||
width: 150
|
width: 140
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
implicitHeight: 10
|
implicitHeight: 10
|
||||||
|
@ -577,7 +573,7 @@ ApplicationWindow {
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
}
|
}
|
||||||
delegate: CustomMenuItem {
|
delegate: CustomMenuItem {
|
||||||
width: 100
|
width: parent.width
|
||||||
}
|
}
|
||||||
Action {
|
Action {
|
||||||
text: "Track Menu"
|
text: "Track Menu"
|
||||||
|
@ -621,7 +617,7 @@ ApplicationWindow {
|
||||||
Menu {
|
Menu {
|
||||||
id: viewMenuBarItem
|
id: viewMenuBarItem
|
||||||
title: "View"
|
title: "View"
|
||||||
width: 100
|
width: 120
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
implicitHeight: 10
|
implicitHeight: 10
|
||||||
|
@ -629,6 +625,7 @@ ApplicationWindow {
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
}
|
}
|
||||||
delegate: CustomMenuItem {
|
delegate: CustomMenuItem {
|
||||||
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
Action {
|
Action {
|
||||||
|
@ -716,8 +713,6 @@ ApplicationWindow {
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
visible: false
|
visible: false
|
||||||
anchors.centerIn: player
|
anchors.centerIn: player
|
||||||
anchors.right: player.right
|
|
||||||
anchors.bottom: progressBar.top
|
|
||||||
border.color: "black"
|
border.color: "black"
|
||||||
border.width: 2
|
border.width: 2
|
||||||
|
|
||||||
|
|
BIN
src/qml/main.qmlc
Normal file
BIN
src/qml/main.qmlc
Normal file
Binary file not shown.
Loading…
Reference in a new issue