Reworked the way qml create sliders 17/19017/3
authorLoïc Collignon <loic.collignon@iot.bzh>
Tue, 18 Dec 2018 16:16:35 +0000 (17:16 +0100)
committerLoïc Collignon [ IoT.bzh ] <loic.collignon@iot.bzh>
Wed, 19 Dec 2018 10:06:56 +0000 (10:06 +0000)
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>
app/CMakeLists.txt
app/Mixer.qml
app/VolumeSlider.qml
app/audiorole.cpp [new file with mode: 0644]
app/audiorole.hpp [new file with mode: 0644]
app/main.cpp
app/mixer.cpp
app/mixer.hpp [moved from app/mixer.h with 80% similarity]

index d23c33a..0259a79 100644 (file)
@@ -35,7 +35,8 @@ endif()
 add_executable(mixer
        "main.cpp"
        "mixer.cpp"
-        "Mixer.qrc"
+       "audiorole.cpp"
+       "Mixer.qrc"
 )
 
 set_target_properties(mixer PROPERTIES
index 73587a5..455743a 100644 (file)
@@ -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 {}
+       }
 }
-
index 130eed4..b44d74c 100644 (file)
@@ -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 (file)
index 0000000..5fad48f
--- /dev/null
@@ -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 (file)
index 0000000..afc3665
--- /dev/null
@@ -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
index c0d4b5b..255b39d 100644 (file)
@@ -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
index 6627987..d85a431 100644 (file)
 #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);
 }
similarity index 80%
rename from app/mixer.h
rename to app/mixer.hpp
index bc1b740..73f31fc 100644 (file)
 #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();
 };