[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
|
||||
linuxdeploy*
|
||||
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 "mpvobject.h"
|
||||
|
||||
#ifdef QRC_SOURCE_PATH
|
||||
#include "runtimeqml/runtimeqml.h"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
|
@ -59,6 +61,15 @@ view->setSource(QUrl("qrc:///player/main.qml"));
|
|||
view->show();*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ MpvObject::MpvObject(QQuickItem * parent)
|
|||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
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?
|
||||
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 vid = player.getProperty("vid")
|
||||
|
||||
console.log("Updating Track Menu, Total Tracks: " + tracks)
|
||||
for (track = 0; track <= tracks; track++) {
|
||||
var trackID = player.getProperty("track-list/" + track + "/id")
|
||||
var trackType = player.getProperty("track-list/" + track + "/type")
|
||||
|
@ -155,12 +154,10 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
function skipToNinth(val) {
|
||||
console.log(val)
|
||||
var skipto = 0
|
||||
if (val != 0) {
|
||||
skipto = Math.floor(progressBar.to / 9 * val)
|
||||
}
|
||||
console.log(skipto)
|
||||
player.command(["seek", skipto, "absolute"])
|
||||
}
|
||||
|
||||
|
@ -175,7 +172,6 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
function updateVolume(volume) {
|
||||
console.log(volume)
|
||||
var muted = player.getProperty("mute")
|
||||
|
||||
if (muted || volume === 0) {
|
||||
|
@ -405,7 +401,7 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.width
|
||||
width: parent.width
|
||||
implicitHeight: 10
|
||||
color: "black"
|
||||
opacity: 0.6
|
||||
|
@ -468,7 +464,7 @@ ApplicationWindow {
|
|||
Menu {
|
||||
id: playbackMenuBarItem
|
||||
title: "Playback"
|
||||
width: 100
|
||||
width: 150
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: 10
|
||||
|
@ -476,7 +472,7 @@ ApplicationWindow {
|
|||
opacity: 0.6
|
||||
}
|
||||
delegate: CustomMenuItem {
|
||||
width: 100
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Action {
|
||||
|
@ -569,7 +565,7 @@ ApplicationWindow {
|
|||
Menu {
|
||||
id: tracksMenuBarItem
|
||||
title: "Tracks"
|
||||
width: 150
|
||||
width: 140
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: 10
|
||||
|
@ -577,7 +573,7 @@ ApplicationWindow {
|
|||
opacity: 0.6
|
||||
}
|
||||
delegate: CustomMenuItem {
|
||||
width: 100
|
||||
width: parent.width
|
||||
}
|
||||
Action {
|
||||
text: "Track Menu"
|
||||
|
@ -621,7 +617,7 @@ ApplicationWindow {
|
|||
Menu {
|
||||
id: viewMenuBarItem
|
||||
title: "View"
|
||||
width: 100
|
||||
width: 120
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: 10
|
||||
|
@ -629,6 +625,7 @@ ApplicationWindow {
|
|||
opacity: 0.6
|
||||
}
|
||||
delegate: CustomMenuItem {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Action {
|
||||
|
@ -716,8 +713,6 @@ ApplicationWindow {
|
|||
height: childrenRect.height
|
||||
visible: false
|
||||
anchors.centerIn: player
|
||||
anchors.right: player.right
|
||||
anchors.bottom: progressBar.top
|
||||
border.color: "black"
|
||||
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