aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Collignon <loic.collignon@iot.bzh>2018-12-18 17:16:35 +0100
committerLoïc Collignon [ IoT.bzh ] <loic.collignon@iot.bzh>2018-12-19 10:06:56 +0000
commitd32d36b936cb031ba1b11c67c0d9c6afbdc280b3 (patch)
treea5a09e31bb41ced3ddea5dddf92530d187da4341
parentf8da591c53f17df877e354896dbbf18410c53b6c (diff)
Reworked the way qml create sliders
Use the qml MVC to populate a ListView with components based on a template VolumeSlider. Should now handle potential disconnections, volume changes by third-party and also fix the issue where sliders are set to 0 at startup. Change-Id: I2961d5a1584a121c473ece253faa90a747c64445 Signed-off-by: Loïc Collignon <loic.collignon@iot.bzh>
-rw-r--r--app/CMakeLists.txt3
-rw-r--r--app/Mixer.qml138
-rw-r--r--app/VolumeSlider.qml64
-rw-r--r--app/audiorole.cpp54
-rw-r--r--app/audiorole.hpp38
-rw-r--r--app/main.cpp9
-rw-r--r--app/mixer.cpp124
-rw-r--r--app/mixer.hpp (renamed from app/mixer.h)19
8 files changed, 251 insertions, 198 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index d23c33a..0259a79 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -35,7 +35,8 @@ endif()
add_executable(mixer
"main.cpp"
"mixer.cpp"
- "Mixer.qrc"
+ "audiorole.cpp"
+ "Mixer.qrc"
)
set_target_properties(mixer PROPERTIES
diff --git a/app/Mixer.qml b/app/Mixer.qml
index 73587a5..455743a 100644
--- a/app/Mixer.qml
+++ b/app/Mixer.qml
@@ -21,101 +21,45 @@ import AGL.Demo.Controls 1.0
import Mixer 1.0
ApplicationWindow {
- // ----- Signals
-
- // ----- Properties
- property Component volumeSlider
-
- // ----- Setup
- id: root
- width: container.width * container.scale
- height: container.height * container.scale
-
- // ----- Childs
- Mixer {
- // ----- Signals
- signal sliderVolumeChanged(string role, int value)
-
- // ----- Properties
-
- // ----- Setup
- id: mixer
-
- onSliderVolumeChanged: {
- console.log("======role: " + role + ", volume: " + value);
- mixer.setVolume(role, value);
- }
-
- Component.onCompleted: {
- var vs = Qt.createComponent("VolumeSlider.qml");
- if (vs.status !== Component.Ready) {
- console.log("Failed to load the VolumeSlider.qml component: " + vs.errorString());
- }
- root.volumeSlider = vs
- mixer.open(bindingAddress);
- }
-
- onRolesChanged: {
- // Remove existing sliders
- for(var i = sliders.children.length; i > 0 ; --i) {
- console.log("destroying: " + i);
- sliders.children[i-1].destroy();
- }
-
- // Add slider for each role
- for(var j = 0; j < mixer.roles.length; ++j) {
- addSlider(mixer.roles[j]);
- }
- }
-
- onVolumeChanged: {
- console.log("onVolumeChanged(\"" + name + "\", " + value + ")");
- for(var i = 0; i < sliders.children.length ; i++) {
- var sld = sliders.children[i];
- console.log(i + " - Slider found:" + sld + "[\"" + sld.role + "\"] = " + sld.value);
- if (sld.role === name) {
- sld.value = value;
- }
- }
- }
-
- // ----- Functions
- function addSlider(name) {
- var sld = root.volumeSlider.createObject(sliders)
- sld.role = name
- sld.onSliderValueChanged.connect(mixer.sliderVolumeChanged)
- mixer.getVolume(name); // Update volume
- }
-
- function deleteChilds(item) {
- for(var i = item.children.length; i > 0 ; i--) {
- deleteChilds(item.children[i-1]);
- }
- item.destroy();
- }
- }
-
- Item {
- id: container
- anchors.centerIn: parent
- width: 1080
- height: 1487
- scale: screenInfo.scale_factor()
-
- Label {
- id: title
- font.pixelSize: 48
- text: "Mixer"
- anchors.horizontalCenter: parent.horizontalCenter
- }
-
- ColumnLayout {
- id: sliders
- anchors.margins: 80
- anchors.top: title.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- }
- }
+ // ----- Signals
+
+ // ----- Properties
+ property Component volumeSlider
+
+ // ----- Setup
+ id: root
+ width: 1080 * roles.scale
+ height: 1487 * roles.scale
+
+ // ----- Childs
+ Label {
+ id: title
+ font.pixelSize: 48
+ text: "Mixer"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Mixer {
+ signal sliderVolumeChanged(string role, int value)
+
+ id: mixer
+
+ Component.onCompleted: {
+ mixer.open(bindingAddress);
+ }
+ }
+
+ ListView {
+ id: roles
+ model: mixer.roles
+ scale: scale_factor
+
+ anchors.margins: 80
+ anchors.top: title.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ delegate: VolumeSlider {}
+ }
}
-
diff --git a/app/VolumeSlider.qml b/app/VolumeSlider.qml
index 130eed4..b44d74c 100644
--- a/app/VolumeSlider.qml
+++ b/app/VolumeSlider.qml
@@ -1,39 +1,39 @@
+import QtQuick 2.6
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0
RowLayout {
- property int value
- property string role
- signal sliderValueChanged(string role, int value)
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Layout.minimumHeight: 75
- onRoleChanged: sliderName.text = role
- onValueChanged: {
- sliderValue.text = value + " %"
- sliderControl.value = value;
- }
+ Label {
+ font.pixelSize: 24
+ text: modelData.name
+ Layout.minimumWidth: 150
+ Layout.maximumWidth: 150
+ elide: Text.ElideRight
+ }
+ Label {
+ font.pixelSize: 24
+ text: modelData.value + " %"
+ Layout.minimumWidth: 75
+ Layout.maximumWidth: 75
+ elide: Text.ElideRight
+ }
+ Slider {
+ id: roleValueSlider
+ Layout.fillWidth: true
+ from: 0
+ to: 100
+ value: modelData.value
+ stepSize: 1
+ snapMode: Slider.SnapOnRelease
+ }
- Layout.minimumHeight: 75
- Label {
- id: sliderName
- font.pixelSize: 24
- text: role
- Layout.minimumWidth: 150
- }
- Label {
- id: sliderValue
- font.pixelSize: 24
- text: "0 %"
- }
- Slider {
- id: sliderControl
- Layout.fillWidth: true
- from: 0
- to: 100
- stepSize: 1
- snapMode: Slider.SnapOnRelease
- onValueChanged: {
- sliderValue.text = value + " %";
- sliderValueChanged(role, value);
- }
- }
+ Binding {
+ target: modelData
+ property: "value"
+ value: roleValueSlider.value
+ }
}
diff --git a/app/audiorole.cpp b/app/audiorole.cpp
new file mode 100644
index 0000000..5fad48f
--- /dev/null
+++ b/app/audiorole.cpp
@@ -0,0 +1,54 @@
+#include "audiorole.hpp"
+
+AudioRole::AudioRole(QObject* parent)
+ : QObject(parent)
+ , m_Name{""}
+ , m_Value{0}
+ , m_Updating{0}
+{
+}
+
+AudioRole::AudioRole(const QString& name, int value, QObject* parent)
+ : QObject(parent)
+ , m_Name{name}
+ , m_Value{value}
+ , m_Updating{0}
+{
+}
+
+QString AudioRole::Name() const
+{
+ return m_Name;
+}
+
+void AudioRole::setName(const QString& name)
+{
+ m_Name = name;
+ emit NameChanged();
+}
+
+int AudioRole::Value() const
+{
+ return m_Value;
+}
+
+void AudioRole::setValue(int value)
+{
+ if (m_Value != value)
+ {
+ m_Value = value;
+ if (m_Updating == 0)
+ emit ValueChanged();
+ }
+}
+
+void AudioRole::BeginUpdate()
+{
+ m_Updating++;
+}
+
+void AudioRole::EndUpdate()
+{
+ if (m_Updating > 0) m_Updating--;
+ //if (m_Updating == 0) emit ValueChanged();
+}
diff --git a/app/audiorole.hpp b/app/audiorole.hpp
new file mode 100644
index 0000000..afc3665
--- /dev/null
+++ b/app/audiorole.hpp
@@ -0,0 +1,38 @@
+#ifndef AUDIOROLE_H
+#define AUDIOROLE_H
+
+#include <QObject>
+
+class AudioRole
+ : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ Name WRITE setName NOTIFY NameChanged)
+ Q_PROPERTY(int value READ Value WRITE setValue NOTIFY ValueChanged)
+
+private:
+ QString m_Name;
+ int m_Value;
+ int m_Updating;
+
+public:
+ explicit AudioRole(QObject* parent = nullptr);
+ explicit AudioRole(const QString& name, int value, QObject* parent = nullptr);
+
+ QString Name() const;
+ void setName(const QString& name);
+
+ int Value() const;
+ void setValue(int value);
+
+ void BeginUpdate();
+ void EndUpdate();
+
+signals:
+ void NameChanged();
+ void ValueChanged();
+
+public slots:
+};
+
+#endif // AUDIOROLE_H
diff --git a/app/main.cpp b/app/main.cpp
index c0d4b5b..255b39d 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -20,6 +20,7 @@
#include <QtCore/QCommandLineParser>
#include <QtCore/QUrlQuery>
#include <QtCore/QDir>
+#include <QtCore/QList>
#include <QtCore/QStandardPaths>
#include <QtCore/QThread>
#include <QtGui/QGuiApplication>
@@ -36,7 +37,8 @@
#else
#include <QScreen>
#endif
-#include "mixer.h"
+#include "mixer.hpp"
+#include "audiorole.hpp"
int main(int argc, char *argv[])
{
@@ -82,7 +84,7 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
AGLScreenInfo screenInfo(qwm->get_scale_factor());
- engine.rootContext()->setContextProperty(QStringLiteral("screenInfo"), &screenInfo);
+ engine.rootContext()->setContextProperty(QStringLiteral("scale_factor"), screenInfo.scale_factor());
// Request a surface as described in layers.json windowmanager’s file
if (qwm->requestSurface(graphic_role) != 0) {
exit(EXIT_FAILURE);
@@ -101,8 +103,9 @@ int main(int argc, char *argv[])
qDebug("Surface %s got tapShortcut\n", graphic_role.toStdString().c_str());
qwm->activateWindow(graphic_role);
});
+#else
+ engine.rootContext()->setContextProperty(QStringLiteral("scale_factor"), 1.0);
#endif
-
engine.load(QUrl(QStringLiteral("qrc:/Mixer.qml")));
// Find the instantiated model QObject and connect the signals/slots
diff --git a/app/mixer.cpp b/app/mixer.cpp
index 6627987..d85a431 100644
--- a/app/mixer.cpp
+++ b/app/mixer.cpp
@@ -20,63 +20,94 @@
#include <QJsonObject>
#include <QTimer>
#include <QtDebug>
-#include "mixer.h"
+#include "mixer.hpp"
Mixer::Mixer(QObject* parent)
- : QObject(parent)
+ : QObject(parent)
{
connect(&m_client, SIGNAL(connected()), this, SLOT(onClientConnected()));
connect(&m_client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected()));
connect(&m_client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onClientError(QAbstractSocket::SocketError)));
connect(&m_client, SIGNAL(eventReceived(QString, const QJsonValue&)), this, SLOT(onClientEventReceived(QString, const QJsonValue&)));
+}
- m_roles.append("Multimedia");
- m_roles.append("Navigation");
- m_roles.append("Emergency");
+void Mixer::open(const QUrl& url)
+{
+ m_url = url;
+ TryOpen();
}
-QStringList Mixer::roles() const
+QList<QObject*> Mixer::roles() const
{
return m_roles;
}
-void Mixer::open(const QUrl& url)
+void Mixer::getRoleVolume(AudioRole* role)
{
- m_url = url;
- m_client.open(m_url);
+ if (role == nullptr) return;
+
+ QJsonObject arg;
+ arg.insert("action", "volume");
+ arg.insert("value", QJsonValue("+0")); // FIXME: Hack to get volume: ask for a relative change with a delta of zero
+
+ m_client.call("ahl-4a", role->Name().toLocal8Bit().data(), arg, [role](bool r, const QJsonValue& v) {
+ if (r && v.isObject())
+ {
+ qDebug() << role->Name() << " Volume changed: " << v;
+ int newVolume = v.toObject()["response"].toObject()["volnew"].toInt();
+ role->setValue(newVolume);
+ }
+ });
+}
+
+void Mixer::setRoleVolume(AudioRole* role)
+{
+ if (role == nullptr) return;
+ role->BeginUpdate();
+
+ QJsonObject arg;
+ arg.insert("action", "volume");
+ arg.insert("value", QJsonValue(role->Value()));
+ m_client.call("ahl-4a", role->Name().toLocal8Bit().data(), arg, [role](bool r, const QJsonValue& v) {
+ // Nothing to do, events will update sliders
+ role->EndUpdate();
+ });
}
void Mixer::onClientConnected()
{
// Subscribe to 4a events
m_client.call("ahl-4a", "subscribe", QJsonValue(), [this](bool r, const QJsonValue& val) {
- if (r)
- qDebug() << "Mixer::onClientConnected - subscribed to 4a events!";
- else
- qCritical () << "Mixer::onClientConnected - Failed to subscribe to 4a events!";
+ if (r) qDebug() << "Mixer::onClientConnected - subscribed to 4a events!";
+ else qCritical () << "Mixer::onClientConnected - Failed to subscribe to 4a events!";
});
// Call HAL to populate list
m_client.call("ahl-4a", "get_roles", QJsonValue(), [this](bool r, const QJsonValue& val) {
- if (r)
- {
+ if (r)
+ {
+ for(QObject* role : m_roles) delete role;
m_roles.clear();
- //BUG: should be able to add this, but not handled right now: m_roles.append("playback");
+
QJsonArray cards = val.toObject()["response"].toArray();
foreach (const QJsonValue& card, cards)
{
- m_roles.append(card.toString());
+ AudioRole* ar = new AudioRole(card.toString(), 0);
+ getRoleVolume(reinterpret_cast<AudioRole*>(ar));
+ connect(ar, SIGNAL(ValueChanged()), this, SLOT(onRoleValueChanged()));
+ m_roles.append(ar);
qDebug() << "Mixer::onClientConnected - added this HAL: " << card.toString();
}
emit rolesChanged();
}
});
+
}
void Mixer::onClientDisconnected()
{
- qDebug() << "Mixer::onClientDisconnected";
- QTimer::singleShot(1000, this, SLOT(onRetryOpen()));
+ qDebug() << "Mixer::onClientDisconnected!";
+ QTimer::singleShot(1000, this, SLOT(TryOpen()));
}
void Mixer::onClientError(QAbstractSocket::SocketError se)
@@ -84,54 +115,33 @@ void Mixer::onClientError(QAbstractSocket::SocketError se)
qDebug() << "Mixer::onClientError: " << se;
}
-void Mixer::onRetryOpen()
-{
- m_client.open(m_url);
-}
-
void Mixer::onClientEventReceived(QString eventName, const QJsonValue& data)
{
qDebug() << "Mixer::onClientEventReceived[" << eventName << "]: " << data;
if (eventName == "ahl-4a/volume_changed")
{
- QString role = data["role"].toString();
- int volume = data["volume"].toInt();
- m_volumes[role] = volume;
- emit volumeChanged(role, volume);
+ QString role = data.toObject()["role"].toString();
+ int volume = data.toObject()["volume"].toInt();
+ for(QObject* o : m_roles)
+ {
+ AudioRole* ar = reinterpret_cast<AudioRole*>(o);
+ if (ar && ar->Name() == role)
+ {
+ ar->setValue(volume);
+ }
+ }
}
}
-void Mixer::setVolume(const QString& name, int value)
+void Mixer::onRoleValueChanged()
{
- auto currentVolume = m_volumes.find(name);
- if (currentVolume != m_volumes.end() && *currentVolume == value)
- return;
-
- QJsonObject arg;
- arg.insert("action", "volume");
- arg.insert("value", QJsonValue(value));
- m_client.call("ahl-4a", name, arg, [name](bool r, const QJsonValue& v) {
- /* Nothing to do, events will update sliders*/
- });
+ AudioRole* role = reinterpret_cast<AudioRole*>(QObject::sender());
+ if (role == nullptr) return;
+ setRoleVolume(role);
}
-void Mixer::getVolume(const QString& name)
+void Mixer::TryOpen()
{
- QJsonObject arg;
- arg.insert("action", "volume");
- arg.insert("value", QJsonValue("+0")); // FIXME: Hack to get volume: ask for a relative change with a delta of zero
- m_client.call("ahl-4a", name, arg, [this, name](bool r, const QJsonValue& v) {
- if (r && v.isObject())
- {
- // TODO: Success, update the slider
- qDebug() << "Volume changed: " << v;
- int newVolume = v.toObject()["response"].toObject()["volnew"].toInt();
- auto currentVolume = m_volumes.find(name);
- if (currentVolume != m_volumes.end() && *currentVolume == newVolume)
- return;
-
- m_volumes[name] = newVolume;
- emit volumeChanged(name, newVolume);
- }
- });
+ qDebug() << "Mixer::TryOpen: " << m_url;
+ m_client.open(m_url);
}
diff --git a/app/mixer.h b/app/mixer.hpp
index bc1b740..73f31fc 100644
--- a/app/mixer.h
+++ b/app/mixer.hpp
@@ -20,20 +20,20 @@
#include <QObject>
#include <QString>
#include <QSharedPointer>
-#include <QStringList>
+#include <QList>
#include <QMap>
#include "qafbwebsocketclient.h"
+#include "audiorole.hpp"
class Mixer
: public QObject
{
Q_OBJECT
- Q_PROPERTY(QStringList roles READ roles NOTIFY rolesChanged)
+ Q_PROPERTY(QList<QObject*> roles READ roles NOTIFY rolesChanged)
private:
QUrl m_url;
- QMap<QString, int> m_volumes;
- QStringList m_roles;
+ QList<QObject*> m_roles;
QAfbWebsocketClient m_client;
public:
@@ -41,9 +41,9 @@ public:
Mixer(const Mixer&) = delete;
Q_INVOKABLE void open(const QUrl& url);
- Q_INVOKABLE QStringList roles() const;
- Q_INVOKABLE void setVolume(const QString& name, int value);
- Q_INVOKABLE void getVolume(const QString& name);
+ Q_INVOKABLE QList<QObject*> roles() const;
+ Q_INVOKABLE void getRoleVolume(AudioRole* role);
+ Q_INVOKABLE void setRoleVolume(AudioRole* role);
signals:
void rolesChanged();
@@ -53,6 +53,9 @@ private slots:
void onClientConnected();
void onClientDisconnected();
void onClientError(QAbstractSocket::SocketError se);
- void onRetryOpen();
void onClientEventReceived(QString eventName, const QJsonValue& data);
+
+ void onRoleValueChanged();
+
+ void TryOpen();
};