Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QML Pt. 1: Add initial support for loading QML skins #3894

Merged
merged 14 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/skin/legacy/legacyskinparser.cpp
src/skin/legacy/pixmapsource.cpp
src/skin/legacy/legacyskin.cpp
src/skin/qml/qmlcontrolproxy.cpp
src/skin/qml/qmlskin.cpp
src/skin/legacy/skincontext.cpp
src/skin/legacy/tooltips.cpp
src/skin/skinloader.cpp
Expand Down Expand Up @@ -1978,6 +1980,7 @@ find_package(Qt5
Network
OpenGL
Qml
QuickWidgets
Sql
Svg
Test
Expand All @@ -1992,6 +1995,7 @@ target_link_libraries(mixxx-lib PUBLIC
Qt5::Network
Qt5::OpenGL
Qt5::Qml
Qt5::QuickWidgets
Qt5::Sql
Qt5::Svg
Qt5::Test
Expand Down
1 change: 1 addition & 0 deletions src/mixxxapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ MixxxApplication::MixxxApplication(int& argc, char** argv)
m_rightPressedButtons(0),
m_pTouchShift(nullptr) {
registerMetaTypes();
setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);

// Increase the size of the global thread pool to at least
// 4 threads, even if less cores are available. These threads
Expand Down
177 changes: 177 additions & 0 deletions src/skin/qml/qmlcontrolproxy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#include "skin/qml/qmlcontrolproxy.h"

#include "moc_qmlcontrolproxy.cpp"

namespace mixxx {
namespace skin {
namespace qml {

namespace {
constexpr double kDefaultValue = 0.0;
constexpr double kDefaultParameter = 0.0;
} // namespace

QmlControlProxy::QmlControlProxy(QObject* parent)
: QObject(parent),
m_isComponentComplete(false),
m_pControlProxy(nullptr) {
}

void QmlControlProxy::componentComplete() {
m_isComponentComplete = true;
reinitializeOrReset();
}

bool QmlControlProxy::isKeyValid() const {
return m_coKey.isValid();
}

bool QmlControlProxy::isInitialized() const {
return m_pControlProxy != nullptr;
}

void QmlControlProxy::setGroup(const QString& group) {
if (m_coKey.group == group) {
return;
}

const bool keyValidBeforeSet = isKeyValid();
m_coKey.group = group;
emit groupChanged(group);

const bool keyValidAfterSet = isKeyValid();
if (keyValidBeforeSet != keyValidAfterSet) {
emit keyValidChanged(keyValidAfterSet);
}

reinitializeOrReset();
}

const QString& QmlControlProxy::getGroup() const {
return m_coKey.group;
}

void QmlControlProxy::setKey(const QString& key) {
if (m_coKey.item == key) {
return;
}

const bool keyValidBeforeSet = isKeyValid();
m_coKey.item = key;
emit keyChanged(key);

const bool keyValidAfterSet = isKeyValid();
if (keyValidBeforeSet != keyValidAfterSet) {
emit keyValidChanged(keyValidAfterSet);
}

reinitializeOrReset();
}

const QString& QmlControlProxy::getKey() const {
return m_coKey.item;
}

void QmlControlProxy::setValue(double newValue) {
if (!isInitialized()) {
emit valueChanged(kDefaultValue);
return;
}
m_pControlProxy->set(newValue);
slotControlProxyValueChanged(newValue);
}

double QmlControlProxy::getValue() const {
if (!isInitialized()) {
return kDefaultValue;
}
return m_pControlProxy->get();
}

void QmlControlProxy::setParameter(double newValue) {
if (!isInitialized()) {
emit parameterChanged(kDefaultValue);
return;
}
m_pControlProxy->setParameter(newValue);
emit valueChanged(m_pControlProxy->get());
emit parameterChanged(newValue);
}

double QmlControlProxy::getParameter() const {
if (!isInitialized()) {
return kDefaultParameter;
}
return m_pControlProxy->getParameter();
}

void QmlControlProxy::reinitializeOrReset() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is orReset still valid?
It look like the function returns early if it is already initialized

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrReset is confusing, because resetting a control proxy is different from resetting a smart pointer. It is also not the alternative to initialize.

How about:
ReinitializeFromKey()

Than it is clear that one cane assume that it becomes invalid if the key is invalid

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Just ignore this if the component is still loading, because group or key may not be set yet.
if (!m_isComponentComplete) {
return;
}

// We don't need to reinitialize or reset the underlying control proxy if
// the CO key didn't change.
if (isInitialized() && m_coKey == m_pControlProxy->getKey()) {
return;
}

// If the key is invalid, reset the control proxy if necessary.
if (!isKeyValid()) {
qWarning() << "QmlControlProxy: Tried to initialize CO" << m_coKey
<< " with invalid key, resetting...";
if (isInitialized()) {
m_pControlProxy.reset();
emit initializedChanged(false);
}
return;
}

// We don't need to warn here if the control is missing, because we'll do a
// check below and print a warning anyway. If the key is invalid, this will
// still trigger an assertion because we checked the key validity above. If
// it's still invalid, that's a programming error.
std::unique_ptr<ControlProxy> pControlProxy =
std::make_unique<ControlProxy>(
m_coKey, this, ControlFlag::NoWarnIfMissing);

// This should never happen, but it doesn't hurt to check.
VERIFY_OR_DEBUG_ASSERT(pControlProxy != nullptr) {
qWarning() << "QmlControlProxy: Requested CO " << m_coKey
<< " returned nullptr, resetting...";
if (isInitialized()) {
m_pControlProxy.reset();
emit initializedChanged(false);
}
return;
}

// If the control does not exist, reset the control proxy if necessary.
if (!pControlProxy->valid()) {
qWarning() << "QmlControlProxy: Requested CO" << m_coKey << " does not exist, resetting...";
if (isInitialized()) {
m_pControlProxy.reset();
emit initializedChanged(false);
}
return;
}

// Set the control proxy and emit signal
const bool wasInitialized = isInitialized();
m_pControlProxy = std::move(pControlProxy);
if (!wasInitialized) {
emit initializedChanged(true);
}
m_pControlProxy->connectValueChanged(this, &QmlControlProxy::slotControlProxyValueChanged);
slotControlProxyValueChanged(m_pControlProxy->get());
}

void QmlControlProxy::slotControlProxyValueChanged(double newValue) {
emit valueChanged(newValue);
emit parameterChanged(m_pControlProxy->getParameter());
}

} // namespace qml
} // namespace skin
} // namespace mixxx
89 changes: 89 additions & 0 deletions src/skin/qml/qmlcontrolproxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#pragma once

#include <QObject>
#include <QQmlParserStatus>
#include <QtQml>
#include <memory>

#include "control/controlproxy.h"

namespace mixxx {
namespace skin {
namespace qml {

class QmlControlProxy : public QObject, public QQmlParserStatus {
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
// The REQUIRED flag only exists in Qt 5.14 and later.
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
Q_PROPERTY(QString group READ getGroup WRITE setGroup NOTIFY groupChanged REQUIRED)
Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged REQUIRED)
#else
Q_PROPERTY(QString group READ getGroup WRITE setGroup NOTIFY groupChanged)
Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged)
#endif
Q_PROPERTY(QString keyValid READ isKeyValid NOTIFY keyValidChanged)
Q_PROPERTY(QString initialized READ isInitialized NOTIFY initializedChanged)
Q_PROPERTY(double value READ getValue WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(double parameter READ getParameter WRITE setParameter NOTIFY parameterChanged)

public:
explicit QmlControlProxy(QObject* parent = nullptr);

/// Implementing the QQmlParserStatus interface requires overriding this
/// method, but we don't need it.
// Invoked after class creation, but before any properties have been set.
void classBegin() override{};

/// QML cannot pass arguments to C++ constructors so this class needs to
/// rely on the QML object setting the group and key properties to
/// initialize the ControlProxy. We want to deplay the initialization of
/// the underlying ControlProxy until the object has been fully created and
/// all properties (group and key in particular) have been set. Perform
/// some initialization here now that the object is fully created.
void componentComplete() override;

void setGroup(const QString& group);
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
const QString& getGroup() const;

void setKey(const QString& key);
const QString& getKey() const;

void setValue(double newValue);
double getValue() const;

void setParameter(double newValue);
double getParameter() const;

bool isKeyValid() const;
bool isInitialized() const;

signals:
void groupChanged(const QString& group);
void keyChanged(const QString& key);
void keyValidChanged(bool valid);
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
void initializedChanged(bool initialized);
void valueChanged(double newValue);
void parameterChanged(double newParameter);

private slots:
/// Emits both the valueChanged and parameterChanged signals
void slotControlProxyValueChanged(double newValue);
uklotzde marked this conversation as resolved.
Show resolved Hide resolved

private:
/// (Re-)Initializes or resets the underlying control proxy. Called for the first time when
/// component construction has been completed. From that moment on, it's
/// called whenever the group or key changes.
void reinitializeOrReset();

ConfigKey m_coKey;

/// Set to true in the componentComplete() method, which is called when the
/// QML object creation is complete.
bool m_isComponentComplete;
std::unique_ptr<ControlProxy> m_pControlProxy;
};

} // namespace qml
} // namespace skin
} // namespace mixxx
Loading