From d32d36b936cb031ba1b11c67c0d9c6afbdc280b3 Mon Sep 17 00:00:00 2001 From: Loïc Collignon Date: Tue, 18 Dec 2018 17:16:35 +0100 Subject: Reworked the way qml create sliders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/CMakeLists.txt | 3 +- app/Mixer.qml | 138 +++++++++++++++------------------------------------ app/VolumeSlider.qml | 64 ++++++++++++------------ app/audiorole.cpp | 54 ++++++++++++++++++++ app/audiorole.hpp | 38 ++++++++++++++ app/main.cpp | 9 ++-- app/mixer.cpp | 124 ++++++++++++++++++++++++--------------------- app/mixer.h | 58 ---------------------- app/mixer.hpp | 61 +++++++++++++++++++++++ 9 files changed, 301 insertions(+), 248 deletions(-) create mode 100644 app/audiorole.cpp create mode 100644 app/audiorole.hpp delete mode 100644 app/mixer.h create mode 100644 app/mixer.hpp 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 + +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 #include #include +#include #include #include #include @@ -36,7 +37,8 @@ #else #include #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 #include #include -#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 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(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(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(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.h deleted file mode 100644 index bc1b740..0000000 --- a/app/mixer.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016 The Qt Company Ltd. - * Copyright (C) 2016,2017 Konsulko Group - * Copyright (C) 2018 IoT.bzh - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include -#include -#include -#include -#include -#include "qafbwebsocketclient.h" - -class Mixer - : public QObject -{ - Q_OBJECT - Q_PROPERTY(QStringList roles READ roles NOTIFY rolesChanged) - -private: - QUrl m_url; - QMap m_volumes; - QStringList m_roles; - QAfbWebsocketClient m_client; - -public: - explicit Mixer(QObject* parent = nullptr); - 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); - -signals: - void rolesChanged(); - void volumeChanged(const QString& name, int value); - -private slots: - void onClientConnected(); - void onClientDisconnected(); - void onClientError(QAbstractSocket::SocketError se); - void onRetryOpen(); - void onClientEventReceived(QString eventName, const QJsonValue& data); -}; diff --git a/app/mixer.hpp b/app/mixer.hpp new file mode 100644 index 0000000..73f31fc --- /dev/null +++ b/app/mixer.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 The Qt Company Ltd. + * Copyright (C) 2016,2017 Konsulko Group + * Copyright (C) 2018 IoT.bzh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include "qafbwebsocketclient.h" +#include "audiorole.hpp" + +class Mixer + : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList roles READ roles NOTIFY rolesChanged) + +private: + QUrl m_url; + QList m_roles; + QAfbWebsocketClient m_client; + +public: + explicit Mixer(QObject* parent = nullptr); + Mixer(const Mixer&) = delete; + + Q_INVOKABLE void open(const QUrl& url); + Q_INVOKABLE QList roles() const; + Q_INVOKABLE void getRoleVolume(AudioRole* role); + Q_INVOKABLE void setRoleVolume(AudioRole* role); + +signals: + void rolesChanged(); + void volumeChanged(const QString& name, int value); + +private slots: + void onClientConnected(); + void onClientDisconnected(); + void onClientError(QAbstractSocket::SocketError se); + void onClientEventReceived(QString eventName, const QJsonValue& data); + + void onRoleValueChanged(); + + void TryOpen(); +}; -- cgit 1.2.3-korg