From 205ab055064295fe5e4eb1ae156cab7b753eb665 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 18 Jan 2016 15:54:20 -0800 Subject: [PATCH] First pass on preferences dialog Conflicts: tests/ui/qmlscratch.pro --- .../qml/hifi/dialogs/PreferencesDialog.qml | 82 +++++ .../hifi/dialogs/preferences/Browsable.qml | 49 +++ .../qml/hifi/dialogs/preferences/CheckBox.qml | 23 ++ .../qml/hifi/dialogs/preferences/Editable.qml | 34 +++ .../hifi/dialogs/preferences/Preference.qml | 9 + .../qml/hifi/dialogs/preferences/Section.qml | 114 +++++++ .../qml/hifi/dialogs/preferences/Slider.qml | 32 ++ .../qml/hifi/dialogs/preferences/SpinBox.qml | 33 ++ interface/src/Application.cpp | 12 +- interface/src/ui/PreferencesDialog.cpp | 288 ++++++++++++++++++ libraries/shared/src/Preferences.cpp | 25 ++ libraries/shared/src/Preferences.h | 248 +++++++++++++++ tests/ui/qmlscratch.pro | 12 +- tests/ui/src/main.cpp | 33 ++ 14 files changed, 991 insertions(+), 3 deletions(-) create mode 100644 interface/resources/qml/hifi/dialogs/PreferencesDialog.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/Browsable.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/Editable.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/Preference.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/Section.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/Slider.qml create mode 100644 interface/resources/qml/hifi/dialogs/preferences/SpinBox.qml create mode 100644 libraries/shared/src/Preferences.cpp create mode 100644 libraries/shared/src/Preferences.h diff --git a/interface/resources/qml/hifi/dialogs/PreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/PreferencesDialog.qml new file mode 100644 index 0000000000..c2ec465a59 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/PreferencesDialog.qml @@ -0,0 +1,82 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs +import Qt.labs.settings 1.0 + +import "../../controls" as HifiControls +import "../../windows" +import "./preferences" + +Window { + id: root + objectName: "Preferences" + title: "Preferences" + resizable: true + destroyOnInvisible: true + width: 500 + height: 577 + property var sections: [] + + function saveAll() { + for (var i = 0; i < sections.length; ++i) { + var section = sections[i]; + section.saveAll(); + } + destroy(); + } + + Rectangle { + anchors.fill: parent + clip: true + color: "white" + + Settings { + category: "Overlay.Preferences" + property alias x: root.x + property alias y: root.y + } + + Component { + id: sectionBuilder + Section { } + } + + Component.onCompleted: { + Preferences.loadAll(); + var categories = Preferences.categories; + for (var i = 0; i < categories.length; ++i) { + var category = categories[i]; + sections.push(sectionBuilder.createObject(prefControls, { name: category })); + } + } + + Flickable { + id: flickable + clip: true + interactive: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: dialogButtons.top + anchors.bottomMargin: 8 + contentHeight: prefControls.height + contentWidth: parent.width + + Column { + id: prefControls + anchors.left: parent.left + anchors.right: parent.right + } + } + Row { + id: dialogButtons + anchors { bottom: parent.bottom; right: parent.right; margins: 8 } + Button { text: "Cancel"; onClicked: root.destroy(); } + Button { + text: "Save all changes" + onClicked: root.saveAll(); + } + } + } +} + diff --git a/interface/resources/qml/hifi/dialogs/preferences/Browsable.qml b/interface/resources/qml/hifi/dialogs/preferences/Browsable.qml new file mode 100644 index 0000000000..5d2c0ccd58 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/Browsable.qml @@ -0,0 +1,49 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "." + +Preference { + id: root + property alias buttonText: button.text + property alias text: dataTextField.text + property alias placeholderText: dataTextField.placeholderText + property real spacing: 8 + height: labelText.height + Math.max(dataTextField.height, button.height) + spacing + + Component.onCompleted: { + dataTextField.text = preference.value; + } + + function save() { + preference.value = dataTextField.text; + preference.save(); + } + + Text { + id: labelText + text: root.label + } + + TextField { + id: dataTextField + placeholderText: root.placeholderText + text: preference.value + anchors { + top: labelText.bottom + left: parent.left + right: button.left + topMargin: root.spacing + rightMargin: root.spacing + } + } + + Button { + id: button + anchors { + right: parent.right; + verticalCenter: dataTextField.verticalCenter + } + text: "Browse" + } +} diff --git a/interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml b/interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml new file mode 100644 index 0000000000..23bd9a76a2 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml @@ -0,0 +1,23 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import "." + +Preference { + id: root + height: checkBox.implicitHeight + + Component.onCompleted: { + checkBox.checked = preference.value; + } + + function save() { + preference.value = checkBox.checked; + preference.save(); + } + + Original.CheckBox { + id: checkBox + anchors.fill: parent + text: root.label + } +} diff --git a/interface/resources/qml/hifi/dialogs/preferences/Editable.qml b/interface/resources/qml/hifi/dialogs/preferences/Editable.qml new file mode 100644 index 0000000000..96d95b6815 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/Editable.qml @@ -0,0 +1,34 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Preference { + id: root + property real spacing: 8 + height: labelText.height + dataTextField.height + spacing + + Component.onCompleted: { + dataTextField.text = preference.value; + } + + function save() { + preference.value = dataTextField.text; + preference.save(); + } + + Text { + id: labelText + text: root.label + } + + TextField { + id: dataTextField + placeholderText: preference.placeholderText + anchors { + top: labelText.bottom + left: parent.left + right: parent.right + topMargin: root.spacing + rightMargin: root.spacing + } + } +} diff --git a/interface/resources/qml/hifi/dialogs/preferences/Preference.qml b/interface/resources/qml/hifi/dialogs/preferences/Preference.qml new file mode 100644 index 0000000000..b3e75240d5 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/Preference.qml @@ -0,0 +1,9 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: root + anchors { left: parent.left; right: parent.right } + property var preference; + property string label: preference ? preference.name : ""; +} diff --git a/interface/resources/qml/hifi/dialogs/preferences/Section.qml b/interface/resources/qml/hifi/dialogs/preferences/Section.qml new file mode 100644 index 0000000000..ec7b46fc9d --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/Section.qml @@ -0,0 +1,114 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Hifi 1.0 + +import "../../../controls" as VrControls +import "." + +Preference { + id: root + property bool expanded: false + property string name: "Header" + property real spacing: 8 + readonly property alias toggle: toggle + readonly property alias header: header + default property alias preferences: contentContainer.children + + function saveAll() { + for (var i = 0; i < d.preferences.length; ++i) { + var preference = d.preferences[i]; + preference.save(); + } + } + + clip: true + children: [ toggle, header, contentContainer ] + height: expanded ? header.height + contentContainer.height + root.spacing * 3 + : Math.max(toggle.height, header.height) + root.spacing * 2 + Behavior on height { PropertyAnimation {} } + + Component.onCompleted: d.buildPreferences(); + + function toggleExpanded() { + root.expanded = !root.expanded; + } + + VrControls.FontAwesome { + id: toggle + anchors { left: parent.left; top: parent.top; margins: root.spacing } + rotation: root.expanded ? 0 : -90 + text: "\uf078" + Behavior on rotation { PropertyAnimation {} } + MouseArea { anchors.fill: parent; onClicked: root.toggleExpanded() } + } + + Text { + id: header + anchors { left: toggle.right; top: parent.top; leftMargin: root.spacing * 2; margins: root.spacing } + font.bold: true + font.pointSize: 16 + color: "#0e7077" + text: root.name + } + + Column { + id: contentContainer + spacing: root.spacing + anchors { left: toggle.right; top: header.bottom; topMargin: root.spacing; right: parent.right; margins: root.spacing } + enabled: root.expanded + visible: root.expanded + clip: true + } + + QtObject { + id: d + property var editableBuilder: Component { Editable { } } + property var browsableBuilder: Component { Browsable { } } + property var spinnerBuilder: Component { SpinBox { } } + property var checkboxBuilder: Component { CheckBox { } } + property var sliderBuilder: Component { Slider { } } + property var preferences: [] + + function buildPreferences() { + var categoryPreferences = Preferences.preferencesByCategory[root.name]; + if (categoryPreferences) { + console.log("Category " + root.name + " with " + categoryPreferences.length + " preferences"); + for (var j = 0; j < categoryPreferences.length; ++j) { + buildPreference(categoryPreferences[j]); + } + } + } + + function buildPreference(preference) { + console.log("\tPreference type " + preference.type + " name " + preference.name) + var builder; + switch (preference.type) { + case Preference.Editable: + builder = editableBuilder; + break; + + case Preference.Browsable: + builder = browsableBuilder; + break; + + case Preference.Spinner: + builder = spinnerBuilder; + break; + + case Preference.Slider: + builder = sliderBuilder; + break; + + case Preference.Checkbox: + builder = checkboxBuilder; + break; + + }; + + if (builder) { + preferences.push(builder.createObject(contentContainer, { preference: preference })); + } + } + } +} + diff --git a/interface/resources/qml/hifi/dialogs/preferences/Slider.qml b/interface/resources/qml/hifi/dialogs/preferences/Slider.qml new file mode 100644 index 0000000000..c11a9408e5 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/Slider.qml @@ -0,0 +1,32 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original + +import "." + +Preference { + id: root + property alias slider: slider + height: slider.height + + Component.onCompleted: { + slider.value = preference.value; + } + + function save() { + preference.value = slider.value; + preference.save(); + } + + + Text { + text: root.label + anchors.verticalCenter: slider.verticalCenter + } + + Original.Slider { + id: slider + value: preference.value + width: 130 + anchors { right: parent.right } + } +} diff --git a/interface/resources/qml/hifi/dialogs/preferences/SpinBox.qml b/interface/resources/qml/hifi/dialogs/preferences/SpinBox.qml new file mode 100644 index 0000000000..9c19b80357 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/preferences/SpinBox.qml @@ -0,0 +1,33 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import "." + +Preference { + id: root + property alias spinner: spinner + height: spinner.height + + + Component.onCompleted: { + spinner.value = preference.value; + } + + function save() { + preference.value = spinner.value; + preference.save(); + } + + Text { + text: root.label + anchors.verticalCenter: spinner.verticalCenter + } + + Original.SpinBox { + id: spinner + decimals: preference.decimals + minimumValue: preference.min + maximumValue: preference.max + width: 100 + anchors { right: parent.right } + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7250f2d89e..c33fbc7f5b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1175,7 +1175,7 @@ void Application::initializeGL() { InfoView::show(INFO_HELP_PATH, true); } - +extern void setupPreferences(); void Application::initializeUi() { AddressBarDialog::registerType(); ErrorDialog::registerType(); @@ -1201,6 +1201,8 @@ void Application::initializeUi() { qApp->quit(); }); + setupPreferences(); + // For some reason there is already an "Application" object in the QML context, // though I can't find it. Hence, "ApplicationInterface" rootContext->setContextProperty("ApplicationInterface", this); @@ -1833,6 +1835,14 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_X: + if (isShifted && isMeta) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->getRootContext()->engine()->clearComponentCache(); + offscreenUi->show(QString("hifi/dialogs/PreferencesDialog.qml"), "PreferencesDialog"); + } + break; + case Qt::Key_B: if (isMeta) { auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 5e2322b5c4..2a0d4062c0 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include "Application.h" #include "DialogsManager.h" @@ -30,6 +33,291 @@ #include "UIUtil.h" #include "scripting/WebWindowClass.h" +void setupPreferences() { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + qmlRegisterType("Hifi", 1, 0, "Preference"); + Preferences* preferences = new Preferences(); + + static const QString AVATAR_BASICS { "Avatar Basics" }; + { + auto getter = [=]()->QString {return myAvatar->getDisplayName(); }; + auto setter = [=](const QString& value) { myAvatar->setDisplayName(value); }; + const QString label = "Avatar display name (optional)"; + auto preference = new EditPreference(AVATAR_BASICS, label, getter, setter, preferences); + preference->setPlaceholderText("Not showing a name"); + preferences->addPreference(preference); + } + + { + auto getter = [=]()->QString {return myAvatar->getCollisionSoundURL(); }; + auto setter = [=](const QString& value) { myAvatar->setCollisionSoundURL(value); }; + const QString label = "Avatar collision sound URL (optional)"; + auto preference = new EditPreference(AVATAR_BASICS, label, getter, setter, preferences); + preference->setPlaceholderText("Enter the URL of a sound to play when you bump into something"); + preferences->addPreference(preference); + } + + { + auto getter = [=]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; + auto setter = [=](const QString& value) { /* FIXME */ }; + auto preference = new BrowsePreference(AVATAR_BASICS, "Appearance: ", getter, setter, preferences); + preferences->addPreference(preference); + } + + { + auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; + auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; + auto preference = new BrowsePreference("Snapshots", "Place my Snapshots here:", getter, setter, preferences); + preferences->addPreference(preference); + } + + // Scripts + { + auto getter = []()->QString { return DependencyManager::get()->getScriptsLocation(); }; + auto setter = [](const QString& value) { DependencyManager::get()->setScriptsLocation(value); }; + preferences->addPreference(new BrowsePreference("Scripts", "Load scripts from this directory:", getter, setter, preferences)); + } + + preferences->addPreference(new ButtonPreference("Scripts", "Load Default Scripts", preferences)); + + + { + auto getter = []()->bool {return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; + auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; + preferences->addPreference(new CheckPreference("Privacy", "Send Data", getter, setter, preferences)); + } + + static const QString LOD_TUNING("Level of Detail Tuning"); + { + auto getter = []()->bool { return DependencyManager::get()->getUseAcuity(); }; + auto setter = [](bool value) { DependencyManager::get()->setUseAcuity(value); }; + preferences->addPreference(new CheckPreference(LOD_TUNING, "Render based on visual acuity", getter, setter, preferences)); + } + + { + auto getter = []()->float { return DependencyManager::get()->getDesktopLODDecreaseFPS(); }; + auto setter = [](float value) { DependencyManager::get()->setDesktopLODDecreaseFPS(value); }; + auto preference = new SpinnerPreference(LOD_TUNING, "Minimum Desktop FPS", getter, setter, preferences); + preference->setMin(0); + preference->setMax(120); + preference->setStep(1); + preferences->addPreference(preference); + } + + { + auto getter = []()->float { return DependencyManager::get()->getHMDLODDecreaseFPS(); }; + auto setter = [](float value) { DependencyManager::get()->setHMDLODDecreaseFPS(value); }; + auto preference = new SpinnerPreference(LOD_TUNING, "Minimum HMD FPS", getter, setter, preferences); + preference->setMin(0); + preference->setMax(120); + preference->setStep(1); + preferences->addPreference(preference); + } + + { + auto getter = []()->float { return 1.0f / DependencyManager::get()->getRenderDistanceInverseHighLimit(); }; + auto setter = [](float value) { DependencyManager::get()->setRenderDistanceInverseHighLimit(1.0f / value); }; + auto preference = new SpinnerPreference(LOD_TUNING, "Minimum Display Distance", getter, setter, preferences); + preference->setMin(5); + preference->setMax(32768); + preference->setStep(1); + preferences->addPreference(preference); + } + + static const QString AVATAR_TUNING { "Avatar Tuning" }; + { + auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; + auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); }; + auto preference = new SpinnerPreference(AVATAR_TUNING, "Real world vertical field of view (angular size of monitor)", getter, setter, preferences); + preference->setMin(1); + preference->setMax(180); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return qApp->getFieldOfView(); }; + auto setter = [](float value) { qApp->setFieldOfView(value); }; + auto preference = new SpinnerPreference(AVATAR_TUNING, "Vertical field of view", getter, setter, preferences); + preference->setMin(1); + preference->setMax(180); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = [=]()->float { return myAvatar->getLeanScale(); }; + auto setter = [=](float value) { myAvatar->setLeanScale(value); }; + auto preference = new SpinnerPreference(AVATAR_TUNING, "Lean scale (applies to Faceshift users)", getter, setter, preferences); + preference->setMin(0); + preference->setMax(99.9f); + preference->setDecimals(2); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = [=]()->float { return myAvatar->getUniformScale(); }; + auto setter = [=](float value) { myAvatar->setTargetScaleVerbose(value); }; // The hell? + auto preference = new SpinnerPreference(AVATAR_TUNING, "Avatar scale (default is 1.0)", getter, setter, preferences); + preference->setMin(0.01f); + preference->setMax(99.9f); + preference->setDecimals(2); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = [=]()->float { return myAvatar->getHead()->getPupilDilation(); }; + auto setter = [=](float value) { myAvatar->getHead()->setPupilDilation(value); }; + preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Pupil dilation", getter, setter, preferences)); + } + { + auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; + auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; + preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Camera binary eyelid threshold", getter, setter, preferences)); + } + { + auto getter = []()->float { return FaceTracker::getEyeDeflection(); }; + auto setter = [](float value) { FaceTracker::setEyeDeflection(value); }; + preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Face tracker eye deflection", getter, setter, preferences)); + } + { + auto getter = []()->QString { return DependencyManager::get()->getHostname(); }; + auto setter = [](const QString& value) { DependencyManager::get()->setHostname(value); }; + auto preference = new EditPreference(AVATAR_TUNING, "Faceshift hostname", getter, setter, preferences); + preference->setPlaceholderText("localhost"); + preferences->addPreference(preference); + } + { + auto getter = [=]()->QString { return myAvatar->getAnimGraphUrl().toString(); }; + auto setter = [=](const QString& value) { myAvatar->setAnimGraphUrl(value); }; + auto preference = new EditPreference(AVATAR_TUNING, "Avatar Animation JSON", getter, setter, preferences); + preference->setPlaceholderText("default"); + preferences->addPreference(preference); + } + + static const QString AUDIO("Audio"); + { + auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getDynamicJitterBuffers(); }; + auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setDynamicJitterBuffers(value); }; + preferences->addPreference(new CheckPreference(AUDIO, "Enable Dynamic Jitter Buffers", getter, setter, preferences)); + } + { + auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getDesiredJitterBufferFrames(); }; + auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setStaticDesiredJitterBufferFrames(value); }; + + auto preference = new SpinnerPreference(AUDIO, "Static Jitter Buffer Frames", getter, setter, preferences); + preference->setMin(0); + preference->setMax(10000); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getMaxFramesOverDesired(); }; + auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setMaxFramesOverDesired(value); }; + auto preference = new SpinnerPreference(AUDIO, "Max Frames Over Desired", getter, setter, preferences); + preference->setMax(10000); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getUseStDevForJitterCalc(); }; + auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setUseStDevForJitterCalc(value); }; + preferences->addPreference(new CheckPreference(AUDIO, "Use Stddev for Dynamic Jitter Calc", getter, setter, preferences)); + } + { + auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getWindowStarveThreshold(); }; + auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setWindowStarveThreshold(value); }; + auto preference = new SpinnerPreference(AUDIO, "Window A Starve Threshold", getter, setter, preferences); + preference->setMax(10000); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getWindowSecondsForDesiredCalcOnTooManyStarves(); }; + auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setWindowSecondsForDesiredCalcOnTooManyStarves(value); }; + auto preference = new SpinnerPreference(AUDIO, "Window A (raise desired on N starves) Seconds)", getter, setter, preferences); + preference->setMax(10000); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return DependencyManager::get()->getReceivedAudioStream().getWindowSecondsForDesiredReduction(); }; + auto setter = [](float value) { DependencyManager::get()->getReceivedAudioStream().setWindowSecondsForDesiredReduction(value); }; + auto preference = new SpinnerPreference(AUDIO, "Window B (desired ceiling) Seconds", getter, setter, preferences); + preference->setMax(10000); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getRepetitionWithFade(); }; + auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setRepetitionWithFade(value); }; + preferences->addPreference(new CheckPreference(AUDIO, "Repetition with Fade", getter, setter, preferences)); + } + { + auto getter = []()->float { return DependencyManager::get()->getOutputBufferSize(); }; + auto setter = [](float value) { DependencyManager::get()->setOutputBufferSize(value); }; + auto preference = new SpinnerPreference(AUDIO, "Output Buffer Size (frames)", getter, setter, preferences); + preference->setMin(1); + preference->setMax(20); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->bool {return DependencyManager::get()->getOutputStarveDetectionEnabled(); }; + auto setter = [](bool value) { DependencyManager::get()->setOutputStarveDetectionEnabled(value); }; + auto preference = new CheckPreference(AUDIO, "Output Starve Detection (Automatic Buffer Size Increase)", getter, setter, preferences); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return DependencyManager::get()->getOutputStarveDetectionThreshold(); }; + auto setter = [](float value) { DependencyManager::get()->setOutputStarveDetectionThreshold(value); }; + auto preference = new SpinnerPreference(AUDIO, "Output Starve Detection Threshold", getter, setter, preferences); + preference->setMin(1); + preference->setMax(500); + preference->setStep(1); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return DependencyManager::get()->getOutputStarveDetectionPeriod(); }; + auto setter = [](float value) { DependencyManager::get()->setOutputStarveDetectionPeriod(value); }; + auto preference = new SpinnerPreference(AUDIO, "Output Starve Detection Period (ms)", getter, setter, preferences); + preference->setMin(1); + preference->setMax(999999999); + preference->setStep(1); + preferences->addPreference(preference); + } + + { + auto getter = []()->float { return qApp->getMaxOctreePacketsPerSecond(); }; + auto setter = [](float value) { qApp->setMaxOctreePacketsPerSecond(value); }; + auto preference = new SpinnerPreference("Octree", "Max packets sent each second", getter, setter, preferences); + preference->setMin(60); + preference->setMax(6000); + preference->setStep(10); + preferences->addPreference(preference); + } + + + { + auto getter = []()->float { return qApp->getApplicationCompositor().getHmdUIAngularSize(); }; + auto setter = [](float value) { qApp->getApplicationCompositor().setHmdUIAngularSize(value); }; + auto preference = new SpinnerPreference("HMD", "User Interface Horizontal Angular Size (degrees)", getter, setter, preferences); + preference->setMin(30); + preference->setMax(160); + preference->setStep(1); + preferences->addPreference(preference); + } + + + { + auto getter = []()->float { return controller::InputDevice::getReticleMoveSpeed(); }; + auto setter = [](float value) { controller::InputDevice::setReticleMoveSpeed(value); }; + auto preference = new SpinnerPreference("Sixense Controllers", "Reticle Movement Speed", getter, setter, preferences); + preference->setMin(0); + preference->setMax(100); + preference->setStep(1); + preferences->addPreference(preference); + } + + DependencyManager::get()->getRootContext()->setContextProperty("Preferences", preferences); +} const int PREFERENCES_HEIGHT_PADDING = 20; diff --git a/libraries/shared/src/Preferences.cpp b/libraries/shared/src/Preferences.cpp new file mode 100644 index 0000000000..feb9e7c3c9 --- /dev/null +++ b/libraries/shared/src/Preferences.cpp @@ -0,0 +1,25 @@ +// +// Created by Bradley Austin Davis 2016/01/20 +// Copyright 2015 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 +// + +#include "Preferences.h" + + +void Preferences::addPreference(Preference* preference) { + const QString& category = preference->getCategory(); + QVariantList categoryPreferences; + // FIXME is there an easier way to do this with less copying? + if (_preferencesByCategory.contains(category)) { + categoryPreferences = qvariant_cast(_preferencesByCategory[category]); + } else { + // Use this property to maintain the order of the categories + _categories.append(category); + } + categoryPreferences.append(QVariant::fromValue(preference)); + _preferencesByCategory[category] = categoryPreferences; +} + diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h new file mode 100644 index 0000000000..b91dcc8256 --- /dev/null +++ b/libraries/shared/src/Preferences.h @@ -0,0 +1,248 @@ +// +// Created by Bradley Austin Davis 2016/01/20 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Shared_Preferences_h +#define hifi_Shared_Preferences_h + +#include +#include +#include +#include +#include + +class Preference; + +class Preferences : public QObject { + Q_OBJECT + Q_PROPERTY(QVariantMap preferencesByCategory READ getPreferencesByCategory CONSTANT) + Q_PROPERTY(QList categories READ getCategories CONSTANT) + +public: + void addPreference(Preference* preference); + const QVariantMap& getPreferencesByCategory() { return _preferencesByCategory; } + const QList& getCategories() { return _categories; } + +private: + QVariantMap _preferencesByCategory; + QList _categories; +}; + +class Preference : public QObject { + Q_OBJECT + Q_PROPERTY(QString category READ getCategory CONSTANT) + Q_PROPERTY(QString name READ getName CONSTANT) + Q_PROPERTY(Type type READ getType CONSTANT) + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) + Q_ENUMS(Type) + +public: + enum Type { + Invalid, + Editable, + Browsable, + Slider, + Spinner, + Checkbox, + Button, + }; + + explicit Preference(QObject* parent = nullptr) : QObject(parent) {} + Preference(const QString& category, const QString& name, QObject* parent = nullptr) + : QObject(parent), _category(category), _name(name) { } + + const QString& getCategory() const { return _category; } + const QString& getName() const { return _name; } + bool isEnabled() const { + return _enabled; + } + + void setEnabled(bool enabled) { + if (enabled != _enabled) { + _enabled = enabled; + emit enabledChanged(); + } + } + + virtual Type getType() { return Invalid; }; + + Q_INVOKABLE virtual void load() {}; + Q_INVOKABLE virtual void save() const {} + +signals: + void enabledChanged(); + +protected: + virtual void emitValueChanged() {}; + + const QString _category; + const QString _name; + bool _enabled { true }; +}; + +class ButtonPreference : public Preference { + Q_OBJECT +public: + ButtonPreference(const QString& category, const QString& name, Preferences* parent = nullptr) + : Preference(category, name, parent) { } + Type getType() { return Button; } + +}; + + +template +class TypedPreference : public Preference { +public: + using Getter = std::function; + using Setter = std::function; + + TypedPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : Preference(category, name, parent), _getter(getter), _setter(setter) { } + + T getValue() const { return _getter(); } + void setValue(const T& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + T oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } + +protected: + T _value; + const Getter _getter; + const Setter _setter; +}; + +class BoolPreference : public TypedPreference { + Q_OBJECT + Q_PROPERTY(bool value READ getValue WRITE setValue NOTIFY valueChanged) + +public: + BoolPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : TypedPreference(category, name, getter, setter, parent) { } + +signals: + void valueChanged(); + +protected: + void emitValueChanged() override { emit valueChanged(); } +}; + +class FloatPreference : public TypedPreference { + Q_OBJECT + Q_PROPERTY(float value READ getValue WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(float min READ getMin CONSTANT) + Q_PROPERTY(float max READ getMax CONSTANT) + Q_PROPERTY(float step READ getStep CONSTANT) + Q_PROPERTY(float decimals READ getDecimals CONSTANT) + +public: + FloatPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : TypedPreference(category, name, getter, setter, parent) { } + + float getMin() const { return _min; } + void setMin(float min) { _min = min; }; + + float getMax() const { return _max; } + void setMax(float max) { _max = max; }; + + float getStep() const { return _step; } + void setStep(float step) { _step = step; }; + + float getDecimals() const { return _decimals; } + void setDecimals(float decimals) { _decimals = decimals; }; + +signals: + void valueChanged(); + +protected: + void emitValueChanged() override { emit valueChanged(); } + + float _decimals { 0 }; + float _min { 0 }; + float _max { 1 }; + float _step { 0.1f }; +}; + +class StringPreference : public TypedPreference { + Q_OBJECT + Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged) + +public: + StringPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : TypedPreference(category, name, getter, setter, parent) { } + +signals: + void valueChanged(); + +protected: + void emitValueChanged() override { emit valueChanged(); } +}; + +class SliderPreference : public FloatPreference { + Q_OBJECT +public: + SliderPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : FloatPreference(category, name, getter, setter, parent) { } + + Type getType() { return Slider; } +}; + +class SpinnerPreference : public FloatPreference { + Q_OBJECT +public: + SpinnerPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : FloatPreference(category, name, getter, setter, parent) { } + + Type getType() { return Spinner; } +}; + +class EditPreference : public StringPreference { + Q_OBJECT + Q_PROPERTY(QString placeholderText READ getPlaceholderText CONSTANT) + +public: + EditPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : StringPreference(category, name, getter, setter, parent) { } + Type getType() { return Editable; } + const QString& getPlaceholderText() const { return _placeholderText; } + void setPlaceholderText(const QString& placeholderText) { _placeholderText = placeholderText; } + +protected: + QString _placeholderText; +}; + +class BrowsePreference : public EditPreference { + Q_OBJECT + Q_PROPERTY(QString browseLabel READ getBrowseLabel CONSTANT) + +public: + BrowsePreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : EditPreference(category, name, getter, setter, parent) { } + Type getType() { return Browsable; } + + const QString& getBrowseLabel() { return _browseLabel; } + void setBrowseLabel(const QString& browseLabel) { _browseLabel = browseLabel; } + +protected: + QString _browseLabel { "Browse" }; +}; + +class CheckPreference : public BoolPreference { + Q_OBJECT +public: + CheckPreference(const QString& category, const QString& name, Getter getter, Setter setter, Preferences* parent = nullptr) + : BoolPreference(category, name, getter, setter, parent) { } + Type getType() { return Checkbox; } +}; + +#endif + + diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index f6859b743a..156833ce4e 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -87,5 +87,13 @@ DISTFILES += \ ../../interface/resources/qml/menus/VrMenuItem.qml \ ../../interface/resources/qml/menus/VrMenuView.qml \ ../../interface/resources/qml/windows/ModalWindow.qml \ - ../../interface/resources/qml/desktop/FocusHack.qml - + ../../interface/resources/qml/desktop/FocusHack.qml \ + ../../interface/resources/qml/hifi/dialogs/PreferencesDialog.qml \ + ../../interface/resources/qml/hifi/dialogs/Section.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/Browsable.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/Section.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/Editable.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/Slider.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/Preference.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/SpinBox.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 1e79fca732..70738b974c 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -3,6 +3,36 @@ #include #include + + +class Preference : public QObject { + Q_OBJECT + Q_PROPERTY(QString category READ getCategory() CONSTANT) + Q_PROPERTY(QString name READ getName() CONSTANT) + Q_PROPERTY(Type type READ getType() CONSTANT) + Q_ENUMS(Type) +public: + enum Type { + Editable, + Browsable, + Spinner, + Checkbox, + }; + + Preference(QObject* parent = nullptr) : QObject(parent) {} + + Preference(const QString& category, const QString& name, QObject* parent = nullptr) + : QObject(parent), _category(category), _name(name) { } + const QString& getCategory() const { return _category; } + const QString& getName() const { return _name; } + virtual Type getType() { return Editable; } + +protected: + const QString _category; + const QString _name; +}; + + QString getRelativeDir(const QString& relativePath = ".") { QDir path(__FILE__); path.cdUp(); auto result = path.absoluteFilePath(relativePath); @@ -45,6 +75,7 @@ int main(int argc, char *argv[]) { QDir::setCurrent(getRelativeDir("..")); QtWebEngine::initialize(); + qmlRegisterType("Hifi", 1, 0, "Preference"); QQmlApplicationEngine engine; addImportPath(engine, "../qml"); @@ -63,3 +94,5 @@ int main(int argc, char *argv[]) { engine.load(QUrl(QStringLiteral("qml/main.qml"))); return app.exec(); } + +#include "main.moc"