From fc23c348cb74159a82ea6699806c3c2841ca763b Mon Sep 17 00:00:00 2001
From: Vladyslav Stelmakhovskyi <vladyslav.stelmakhovski@volvocars.com>
Date: Sat, 25 Mar 2017 13:17:38 +0100
Subject: [PATCH] Implemented Audio settings screen for Tablet UI

---
 interface/resources/qml/hifi/Audio.qml        | 253 ++++++++++++++++++
 .../qml/hifi/components/AudioCheckbox.qml     |  29 ++
 .../AudioDeviceScriptingInterface.cpp         |  29 +-
 .../scripting/AudioDeviceScriptingInterface.h |  14 +
 interface/src/ui/AvatarInputs.cpp             |  21 +-
 interface/src/ui/AvatarInputs.h               |  10 +-
 interface/src/ui/overlays/Web3DOverlay.cpp    |   5 +
 scripts/system/audio.js                       |   3 +-
 8 files changed, 357 insertions(+), 7 deletions(-)
 create mode 100644 interface/resources/qml/hifi/Audio.qml
 create mode 100644 interface/resources/qml/hifi/components/AudioCheckbox.qml

diff --git a/interface/resources/qml/hifi/Audio.qml b/interface/resources/qml/hifi/Audio.qml
new file mode 100644
index 0000000000..deb44b9bd5
--- /dev/null
+++ b/interface/resources/qml/hifi/Audio.qml
@@ -0,0 +1,253 @@
+//
+//  Audio.qml
+//  qml/hifi
+//
+//  Audio setup
+//
+//  Created by Vlad Stelmahovsky on 03/22/2017
+//  Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtGraphicalEffects 1.0
+
+import "../styles-uit"
+import "../controls-uit" as HifiControls
+
+import "components"
+
+Rectangle {
+    id: audio;
+
+    //put info text here
+    property alias infoText: infoArea.text
+
+    color: "#404040";
+
+    HifiConstants { id: hifi; }
+    objectName: "AudioWindow"
+
+    property var eventBridge;
+    property string title: "Audio Options"
+    signal sendToScript(var message);
+
+    //set models after Components is shown
+    Component.onCompleted: {
+        refreshTimer.start()
+        refreshTimerOutput.start()
+    }
+
+    Component {
+        id: separator
+        LinearGradient {
+            start: Qt.point(0, 0)
+            end: Qt.point(0, 4)
+            gradient: Gradient {
+                GradientStop { position: 0.0; color: "#303030" }
+                GradientStop { position: 0.33; color: "#252525" }  // Equivalent of darkGray0 over baseGray background.
+                GradientStop { position: 0.5; color: "#303030" }
+                GradientStop { position: 0.6; color: "#454a49" }
+                GradientStop { position: 1.0; color: "#454a49" }
+            }
+            cached: true
+        }
+    }
+
+    Column {
+        anchors { left: parent.left; right: parent.right }
+        spacing: 8
+
+        RalewayRegular {
+            anchors { left: parent.left; right: parent.right; leftMargin: 30 }
+            height: 45
+            size: 20
+            color: "white"
+            text: audio.title
+        }
+
+        Loader {
+            width: parent.width
+            height: 5
+            sourceComponent: separator
+        }
+
+        //connections required to syncronize with Menu
+        Connections {
+            target: AudioDevice
+            onMuteToggled: {
+                audioMute.checkbox.checked = AudioDevice.getMuted()
+            }
+        }
+
+        Connections {
+            target: AvatarInputs
+            onShowAudioToolsChanged: {
+                audioTools.checkbox.checked = showAudioTools
+            }
+        }
+
+        AudioCheckbox {
+            id: audioMute
+            width: parent.width
+            anchors { left: parent.left; right: parent.right; leftMargin: 30 }
+            checkbox.checked: AudioDevice.muted
+            text.text: qsTr("Mute microphone")
+            onCheckBoxClicked: {
+                AudioDevice.muted = checked
+            }
+        }
+
+        AudioCheckbox {
+            id: audioTools
+            width: parent.width
+            anchors { left: parent.left; right: parent.right; leftMargin: 30 }
+            checkbox.checked: AvatarInputs.showAudioTools
+            text.text: qsTr("Show audio level meter")
+            onCheckBoxClicked: {
+                AvatarInputs.showAudioTools = checked
+            }
+        }
+
+        Loader {
+            width: parent.width
+            height: 5
+            sourceComponent: separator
+        }
+
+        Row {
+            anchors { left: parent.left; right: parent.right; leftMargin: 30 }
+            height: 40
+            spacing: 8
+
+            HiFiGlyphs {
+                text: hifi.glyphs.mic
+                color: hifi.colors.primaryHighlight
+                anchors.verticalCenter: parent.verticalCenter
+                font.pointSize: 27
+            }
+            RalewayRegular {
+                anchors.verticalCenter: parent.verticalCenter
+                size: 16
+                color: "#AFAFAF"
+                text: qsTr("CHOOSE INPUT DEVICE")
+            }
+        }
+
+        ListView {
+            Timer {
+                id: refreshTimer
+                interval: 1
+                repeat: false
+                onTriggered: {
+                    //refresh model
+                    inputAudioListView.model = undefined
+                    inputAudioListView.model = AudioDevice.inputAudioDevices
+                }
+            }
+            id: inputAudioListView
+            anchors { left: parent.left; right: parent.right; leftMargin: 70 }
+            height: 125
+            spacing: 16
+            clip: true
+            snapMode: ListView.SnapToItem
+            delegate: AudioCheckbox {
+                width: parent.width
+                checkbox.checked: (modelData === AudioDevice.getInputDevice())
+                text.text: modelData
+                onCheckBoxClicked: {
+                    if (checked) {
+                        AudioDevice.setInputDevice(modelData)
+                        refreshTimer.start()
+                    }
+                }
+            }
+        }
+
+        Loader {
+            width: parent.width
+            height: 5
+            sourceComponent: separator
+        }
+
+        Row {
+            anchors { left: parent.left; right: parent.right; leftMargin: 30 }
+            height: 40
+            spacing: 8
+
+            HiFiGlyphs {
+                text: hifi.glyphs.unmuted
+                color: hifi.colors.primaryHighlight
+                anchors.verticalCenter: parent.verticalCenter
+                font.pointSize: 27
+            }
+            RalewayRegular {
+                anchors.verticalCenter: parent.verticalCenter
+                size: 16
+                color: "#AFAFAF"
+                text: qsTr("CHOOSE OUTPUT DEVICE")
+            }
+        }
+        ListView {
+            id: outputAudioListView
+            Timer {
+                id: refreshTimerOutput
+                interval: 1
+                repeat: false
+                onTriggered: {
+                    //refresh model
+                    outputAudioListView.model = undefined
+                    outputAudioListView.model = AudioDevice.outputAudioDevices
+                }
+            }
+            anchors { left: parent.left; right: parent.right; leftMargin: 70 }
+            height: 250
+            spacing: 16
+            clip: true
+            snapMode: ListView.SnapToItem
+            delegate: AudioCheckbox {
+                width: parent.width
+                checkbox.checked: (modelData === AudioDevice.getOutputDevice())
+                text.text: modelData
+                onCheckBoxClicked: {
+                    if (checked) {
+                        AudioDevice.setOutputDevice(modelData)
+                        refreshTimerOutput.start()
+                    }
+                }
+            }
+        }
+
+        Loader {
+            id: lastSeparator
+            width: parent.width
+            height: 6
+            sourceComponent: separator
+        }
+
+        Row {
+            anchors { left: parent.left; right: parent.right; leftMargin: 30 }
+            height: 40
+            spacing: 8
+
+            HiFiGlyphs {
+                id: infoSign
+                text: hifi.glyphs.info
+                color: "#AFAFAF"
+                anchors.verticalCenter: parent.verticalCenter
+                size: 60
+            }
+            RalewayRegular {
+                id: infoArea
+                width: parent.width - infoSign.implicitWidth - parent.spacing - 10
+                wrapMode: Text.WordWrap
+                anchors.verticalCenter: parent.verticalCenter
+                size: 12
+                color: hifi.colors.baseGrayHighlight
+            }
+        }
+    }
+}
diff --git a/interface/resources/qml/hifi/components/AudioCheckbox.qml b/interface/resources/qml/hifi/components/AudioCheckbox.qml
new file mode 100644
index 0000000000..a8e0441e0a
--- /dev/null
+++ b/interface/resources/qml/hifi/components/AudioCheckbox.qml
@@ -0,0 +1,29 @@
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+
+import "../../styles-uit"
+import "../../controls-uit" as HifiControls
+
+Row {
+    id: row
+    spacing: 16
+    property alias checkbox: cb
+    property alias text: txt
+    signal checkBoxClicked(bool checked)
+
+    HifiControls.CheckBox {
+        id: cb
+        boxSize: 20
+        colorScheme: hifi.colorSchemes.dark
+        anchors.verticalCenter: parent.verticalCenter
+        onClicked: checkBoxClicked(cb.checked)
+    }
+    RalewayBold {
+        id: txt
+        wrapMode: Text.WordWrap
+        width: parent.width - cb.boxSize - 30
+        anchors.verticalCenter: parent.verticalCenter
+        size: 16
+        color: "white"
+    }
+}
diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.cpp b/interface/src/scripting/AudioDeviceScriptingInterface.cpp
index c4dc58f16b..cbb08c0af0 100644
--- a/interface/src/scripting/AudioDeviceScriptingInterface.cpp
+++ b/interface/src/scripting/AudioDeviceScriptingInterface.cpp
@@ -18,6 +18,21 @@ AudioDeviceScriptingInterface* AudioDeviceScriptingInterface::getInstance() {
     return &sharedInstance;
 }
 
+QStringList AudioDeviceScriptingInterface::inputAudioDevices() const
+{
+    return DependencyManager::get<AudioClient>()->getDeviceNames(QAudio::AudioInput).toList();;
+}
+
+QStringList AudioDeviceScriptingInterface::outputAudioDevices() const
+{
+    return DependencyManager::get<AudioClient>()->getDeviceNames(QAudio::AudioOutput).toList();;
+}
+
+bool AudioDeviceScriptingInterface::muted()
+{
+    return getMuted();
+}
+
 AudioDeviceScriptingInterface::AudioDeviceScriptingInterface() {
     connect(DependencyManager::get<AudioClient>().data(), &AudioClient::muteToggled,
             this, &AudioDeviceScriptingInterface::muteToggled);
@@ -31,7 +46,6 @@ bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) {
                               Qt::BlockingQueuedConnection,
                               Q_RETURN_ARG(bool, result),
                               Q_ARG(const QString&, deviceName));
-
     return result;
 }
 
@@ -41,7 +55,6 @@ bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) {
                               Qt::BlockingQueuedConnection,
                               Q_RETURN_ARG(bool, result),
                               Q_ARG(const QString&, deviceName));
-
     return result;
 }
 
@@ -69,7 +82,6 @@ QVector<QString> AudioDeviceScriptingInterface::getOutputDevices() {
     return DependencyManager::get<AudioClient>()->getDeviceNames(QAudio::AudioOutput);
 }
 
-
 float AudioDeviceScriptingInterface::getInputVolume() {
     return DependencyManager::get<AudioClient>()->getInputVolume();
 }
@@ -90,6 +102,17 @@ void AudioDeviceScriptingInterface::toggleMute() {
     DependencyManager::get<AudioClient>()->toggleMute();
 }
 
+void AudioDeviceScriptingInterface::setMuted(bool muted)
+{
+    bool lMuted = getMuted();
+    if (lMuted == muted)
+        return;
+
+    toggleMute();
+    lMuted = getMuted();
+    emit mutedChanged(lMuted);
+}
+
 bool AudioDeviceScriptingInterface::getMuted() {
     return DependencyManager::get<AudioClient>()->isMuted();
 }
diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.h b/interface/src/scripting/AudioDeviceScriptingInterface.h
index 149de9bf56..4d1d47dcba 100644
--- a/interface/src/scripting/AudioDeviceScriptingInterface.h
+++ b/interface/src/scripting/AudioDeviceScriptingInterface.h
@@ -20,9 +20,18 @@ class AudioEffectOptions;
 
 class AudioDeviceScriptingInterface : public QObject {
     Q_OBJECT
+
+    Q_PROPERTY(QStringList inputAudioDevices READ inputAudioDevices NOTIFY inputAudioDevicesChanged)
+    Q_PROPERTY(QStringList outputAudioDevices READ outputAudioDevices NOTIFY outputAudioDevicesChanged)
+    Q_PROPERTY(bool muted READ muted WRITE setMuted NOTIFY mutedChanged)
+
 public:
     static AudioDeviceScriptingInterface* getInstance();
 
+    QStringList inputAudioDevices() const;
+    QStringList outputAudioDevices() const;
+    bool muted();
+
 public slots:
     bool setInputDevice(const QString& deviceName);
     bool setOutputDevice(const QString& deviceName);
@@ -44,12 +53,17 @@ public slots:
     bool getMuted();
     void toggleMute();
     
+    void setMuted(bool muted);
+
 private:
     AudioDeviceScriptingInterface();
 
 signals:
     void muteToggled();
     void deviceChanged();
+    void mutedChanged(bool muted);
+    void inputAudioDevicesChanged(QStringList inputAudioDevices);
+    void outputAudioDevicesChanged(QStringList outputAudioDevices);
 };
 
 #endif // hifi_AudioDeviceScriptingInterface_h
diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp
index 944be4bf9e..37d7a30938 100644
--- a/interface/src/ui/AvatarInputs.cpp
+++ b/interface/src/ui/AvatarInputs.cpp
@@ -43,6 +43,16 @@ AvatarInputs::AvatarInputs(QQuickItem* parent) :  QQuickItem(parent) {
         } \
     }
 
+#define AI_UPDATE_WRITABLE(name, src) \
+    { \
+        auto val = src; \
+        if (_##name != val) { \
+            _##name = val; \
+            qDebug() << "AvatarInputs" << val; \
+            emit name##Changed(val); \
+        } \
+    }
+
 #define AI_UPDATE_FLOAT(name, src, epsilon) \
     { \
         float val = src; \
@@ -59,7 +69,8 @@ void AvatarInputs::update() {
     AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
     AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
     AI_UPDATE(isHMD, qApp->isHMDMode());
-    AI_UPDATE(showAudioTools, Menu::getInstance()->isOptionChecked(MenuOption::AudioTools));
+
+    AI_UPDATE_WRITABLE(showAudioTools, Menu::getInstance()->isOptionChecked(MenuOption::AudioTools));
 
     auto audioIO = DependencyManager::get<AudioClient>();
     const float AUDIO_METER_AVERAGING = 0.5;
@@ -100,6 +111,14 @@ void AvatarInputs::update() {
     //iconColor = PULSE_MIN + (PULSE_MAX - PULSE_MIN) * pulseFactor;
 }
 
+void AvatarInputs::setShowAudioTools(bool showAudioTools) {
+    if (_showAudioTools == showAudioTools)
+        return;
+
+    Menu::getInstance()->setIsOptionChecked(MenuOption::AudioTools, showAudioTools);
+    update();
+}
+
 void AvatarInputs::toggleCameraMute() {
     FaceTracker* faceTracker = qApp->getSelectedFaceTracker();
     if (faceTracker) {
diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h
index 5535469445..0c4fc0f23c 100644
--- a/interface/src/ui/AvatarInputs.h
+++ b/interface/src/ui/AvatarInputs.h
@@ -29,12 +29,17 @@ class AvatarInputs : public QQuickItem {
     AI_PROPERTY(bool, audioClipping, false)
     AI_PROPERTY(float, audioLevel, 0)
     AI_PROPERTY(bool, isHMD, false)
-    AI_PROPERTY(bool, showAudioTools, true)
+
+    Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
 
 public:
     static AvatarInputs* getInstance();
     AvatarInputs(QQuickItem* parent = nullptr);
     void update();
+    bool showAudioTools() const   { return _showAudioTools; }
+
+public slots:
+    void setShowAudioTools(bool showAudioTools);
 
 signals:
     void cameraEnabledChanged();
@@ -43,7 +48,7 @@ signals:
     void audioClippingChanged();
     void audioLevelChanged();
     void isHMDChanged();
-    void showAudioToolsChanged();
+    void showAudioToolsChanged(bool showAudioTools);
 
 protected:
     Q_INVOKABLE void resetSensors();
@@ -52,6 +57,7 @@ protected:
 
 private: 
     float _trailingAudioLoudness{ 0 };
+    bool _showAudioTools { false };
 };
 
 #endif // hifi_AvatarInputs_h
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index fa815421c7..e2490f241e 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -46,6 +46,8 @@
 #include "LODManager.h"
 #include "ui/OctreeStatsProvider.h"
 #include "ui/DomainConnectionModel.h"
+#include "scripting/AudioDeviceScriptingInterface.h"
+#include "ui/AvatarInputs.h"
 
 static const float DPI = 30.47f;
 static const float INCHES_TO_METERS = 1.0f / 39.3701f;
@@ -189,6 +191,9 @@ void Web3DOverlay::loadSourceURL() {
             _webSurface->getRootContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
             _webSurface->getRootContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
             _webSurface->getRootContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
+            _webSurface->getRootContext()->setContextProperty("AudioDevice", AudioDeviceScriptingInterface::getInstance());
+            _webSurface->getRootContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
+
             _webSurface->getRootContext()->setContextProperty("pathToFonts", "../../");
             tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
 
diff --git a/scripts/system/audio.js b/scripts/system/audio.js
index beeb8609d8..7bc8676a2e 100644
--- a/scripts/system/audio.js
+++ b/scripts/system/audio.js
@@ -45,7 +45,8 @@ function onClicked() {
         var entity = HMD.tabletID;
         Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
         shouldActivateButton = true;
-        tablet.gotoMenuScreen("Audio");
+        shouldActivateButton = true;
+        tablet.loadQMLSource("../Audio.qml");
         onAudioScreen = true;
     }
 }