Commit 93362beb authored by Volker Krause's avatar Volker Krause
Browse files

Factor out logic of the telemetry configuration widget

This enables re-use of this in e.g. QML UI.
parent 75c8a55b
......@@ -5,6 +5,7 @@ set(userfeedback_core_srcs
applicationversionsource.cpp
compilerinfosource.cpp
cpuinfosource.cpp
feedbackconfiguicontroller.cpp
localeinfosource.cpp
openglinfosource.cpp
platforminfosource.cpp
......
/*
Copyright (C) 2017 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "feedbackconfiguicontroller.h"
#include "abstractdatasource.h"
#include <QSet>
#include <QVector>
#include <algorithm>
#include <vector>
using namespace UserFeedback;
namespace UserFeedback {
class FeedbackConfigUiControllerPrivate {
public:
FeedbackConfigUiControllerPrivate();
Provider *provider;
std::vector<Provider::StatisticsCollectionMode> telemetryModeMap;
};
}
FeedbackConfigUiControllerPrivate::FeedbackConfigUiControllerPrivate() :
provider(nullptr)
{
}
FeedbackConfigUiController::FeedbackConfigUiController(QObject* parent)
: QObject(parent)
, d(new FeedbackConfigUiControllerPrivate)
{
}
FeedbackConfigUiController::~FeedbackConfigUiController()
{
}
Provider* FeedbackConfigUiController::feedbackProvider() const
{
return d->provider;
}
void FeedbackConfigUiController::setFeedbackProvider(Provider* provider)
{
if (d->provider == provider)
return;
d->provider = provider;
d->telemetryModeMap.clear();
d->telemetryModeMap.reserve(5);
d->telemetryModeMap.push_back(Provider::NoStatistics);
d->telemetryModeMap.push_back(Provider::BasicSystemInformation);
d->telemetryModeMap.push_back(Provider::BasicUsageStatistics);
d->telemetryModeMap.push_back(Provider::DetailedSystemInformation);
d->telemetryModeMap.push_back(Provider::DetailedUsageStatistics);
QSet<Provider::StatisticsCollectionMode> supportedModes;
supportedModes.reserve(d->telemetryModeMap.size());
supportedModes.insert(Provider::NoStatistics);
foreach (const auto &src, provider->dataSources())
supportedModes.insert(src->collectionMode());
for (auto it = d->telemetryModeMap.begin(); it != d->telemetryModeMap.end();) {
if (!supportedModes.contains(*it))
it = d->telemetryModeMap.erase(it);
else
++it;
}
emit providerChanged();
}
int FeedbackConfigUiController::telemetryModeCount() const
{
return d->telemetryModeMap.size();
}
Provider::StatisticsCollectionMode FeedbackConfigUiController::telemetryIndexToMode(int index) const
{
if (index < 0 || index >= telemetryModeCount())
return Provider::NoStatistics;
return d->telemetryModeMap[index];
}
int FeedbackConfigUiController::telemetryModeToIndex(Provider::StatisticsCollectionMode mode) const
{
const auto it = std::lower_bound(d->telemetryModeMap.begin(), d->telemetryModeMap.end(), mode);
if (it == d->telemetryModeMap.end())
return 0;
return std::distance(d->telemetryModeMap.begin(), it);
}
QString FeedbackConfigUiController::telemetryModeDescription(int telemetryIndex) const
{
switch (telemetryIndexToMode(telemetryIndex)) {
case Provider::NoStatistics:
return tr(
"We make this application for you. You can help us improve it by contributing information on how you use it. "
"This allows us to make sure we focus on things that matter to you.\n"
"Contributing statistics is of course entirely anonymous, will not use any kind of unique identifier and "
"will not cover any data you process with this application."
);
case Provider::BasicSystemInformation:
return tr(
"Share basic system information. "
"No unique identification is included, nor data processed with the application."
);
case Provider::BasicUsageStatistics:
return tr(
"Share basic system information and basic statistics on how often you use the application. "
"No unique identification is included, nor data processed with the application."
);
case Provider::DetailedSystemInformation:
return tr(
"Share basic statistics on how often you use the application, as well as detailed information about your system. "
"No unique identification is included, nor data processed with the application."
);
case Provider::DetailedUsageStatistics:
return tr(
"Share detailed system information and statistics on how often individual features of the application are used. "
"No unique identification is included, nor data processed with the application."
);
}
return QString();
}
QString FeedbackConfigUiController::telemetryModeDetails(int telemetryIndex) const
{
if (telemetryIndex <= 0 || telemetryIndex >= telemetryModeCount())
return QString();
auto detailsStr = QStringLiteral("<ul>");
foreach (const auto *src, d->provider->dataSources()) {
if (telemetryIndex >= telemetryModeToIndex(src->collectionMode()) && !src->description().isEmpty())
detailsStr += QStringLiteral("<li>") + src->description() + QStringLiteral("</li>");
}
return detailsStr + QStringLiteral("</ul>");
}
/*
Copyright (C) 2017 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef USERFEEDBACK_FEEDBACKCONFIGUICONTROLLER_H
#define USERFEEDBACK_FEEDBACKCONFIGUICONTROLLER_H
#include "userfeedbackcore_export.h"
#include "provider.h"
#include <QObject>
#include <memory>
namespace UserFeedback {
class FeedbackConfigUiControllerPrivate;
class Provider;
/*! Logic/behavior of the feedback configuration UI.
* This is available for use in e.g. QtQuick-based UIs.
* @see FeedbackConfigWidget
*/
class USERFEEDBACKCORE_EXPORT FeedbackConfigUiController : public QObject
{
Q_OBJECT
/*! The Provider instance we are configuring. */
Q_PROPERTY(UserFeedback::Provider* feedbackProvider READ feedbackProvider WRITE setFeedbackProvider NOTIFY providerChanged)
/*! Amount of telemetry modes supported by the provider. */
Q_PROPERTY(int telemetryModeCount READ telemetryModeCount NOTIFY providerChanged)
public:
FeedbackConfigUiController(QObject *parent = nullptr);
~FeedbackConfigUiController();
/*! Returns the feedback provider to be configured. */
Provider* feedbackProvider() const;
/*! Set the feedback provider to configure. */
void setFeedbackProvider(Provider *provider);
/*! Amount of supported telemetry modes.
* This depends on what type of sources the provider actually has.
*/
int telemetryModeCount() const;
/*! Convert slider index to telemetry mode. */
Q_INVOKABLE UserFeedback::Provider::StatisticsCollectionMode telemetryIndexToMode(int index) const;
/*! Convert telemetry mode to slider index. */
Q_INVOKABLE int telemetryModeToIndex(UserFeedback::Provider::StatisticsCollectionMode mode) const;
/*! Telemetry mode explanation text. */
Q_INVOKABLE QString telemetryModeDescription(int telemetryIndex) const;
/*! Detailed information about the data sources of the given telemetry mode index. */
Q_INVOKABLE QString telemetryModeDetails(int telemetryIndex) const;
Q_SIGNALS:
/*! A provider-related setting has changed. */
void providerChanged();
private:
std::unique_ptr<FeedbackConfigUiControllerPrivate> d;
};
}
#endif // USERFEEDBACK_FEEDBACKCONFIGUICONTROLLER_H
......@@ -20,6 +20,7 @@
#include "qmlpropertyratiosource.h"
#include "qmlproviderextension.h"
#include <feedbackconfiguicontroller.h>
#include <Provider>
#include <SurveyInfo>
......@@ -44,5 +45,7 @@ void QmlPlugin::registerTypes(const char* uri)
qmlRegisterType<QmlUsageTimeSource>(uri, 1, 0, "UsageTimeSource");
qmlRegisterType<QmlPropertyRatioSource>(uri, 1, 0, "PropertyRatioSource");
qmlRegisterType<FeedbackConfigUiController>(uri, 1, 0, "FeedbackConfigUiController");
qRegisterMetaType<SurveyInfo>();
}
......@@ -19,6 +19,7 @@
#include "ui_feedbackconfigwidget.h"
#include <abstractdatasource.h>
#include <feedbackconfiguicontroller.h>
#include <provider.h>
#include <QApplication>
......@@ -33,92 +34,42 @@ using namespace UserFeedback;
namespace UserFeedback {
class FeedbackConfigWidgetPrivate {
public:
FeedbackConfigWidgetPrivate();
int telemetryModeIndex(Provider::StatisticsCollectionMode mode) const;
void telemetrySliderChanged();
void surveySliderChanged();
void applyPalette(QSlider *slider);
Provider *provider;
FeedbackConfigUiController *controller;
std::unique_ptr<Ui::FeedbackConfigWidget> ui;
std::vector<Provider::StatisticsCollectionMode> telemetryModeMap;
};
}
FeedbackConfigWidgetPrivate::FeedbackConfigWidgetPrivate()
: provider(nullptr)
{
}
int FeedbackConfigWidgetPrivate::telemetryModeIndex(Provider::StatisticsCollectionMode mode) const
{
const auto it = std::lower_bound(telemetryModeMap.begin(), telemetryModeMap.end(), mode);
if (it == telemetryModeMap.end())
return 0;
return std::distance(telemetryModeMap.begin(), it);
}
void FeedbackConfigWidgetPrivate::telemetrySliderChanged()
{
if (ui->telemetrySlider->value() == 0) {
ui->telemetryStack->setCurrentWidget(ui->noTelemetryPage);
} else {
ui->telemetryStack->setCurrentWidget(ui->telemetryPage);
}
switch (telemetryModeMap[ui->telemetrySlider->value()]) {
case Provider::NoStatistics:
break;
case Provider::BasicSystemInformation:
ui->telemetryLabel->setText(FeedbackConfigWidget::tr(
"Share basic system information. "
"No unique identification is included, nor data processed with the application."
));
break;
case Provider::BasicUsageStatistics:
ui->telemetryLabel->setText(FeedbackConfigWidget::tr(
"Share basic system information and basic statistics on how often you use the application. "
"No unique identification is included, nor data processed with the application."
));
break;
case Provider::DetailedSystemInformation:
ui->telemetryLabel->setText(FeedbackConfigWidget::tr(
"Share basic statistics on how often you use the application, as well as detailed information about your system. "
"No unique identification is included, nor data processed with the application."
));
case Provider::DetailedUsageStatistics:
ui->telemetryLabel->setText(FeedbackConfigWidget::tr(
"Share detailed system information and statistics on how often individual features of the application are used. "
"No unique identification is included, nor data processed with the application."
));
break;
ui->telemetryLabel->setText(controller->telemetryModeDescription(ui->telemetrySlider->value()));
}
applyPalette(ui->telemetrySlider);
if (!provider)
if (!controller->feedbackProvider())
return;
if (!ui->rawTelemetryButton->isChecked()) {
auto detailsStr = QStringLiteral("<ul>");
foreach (const auto *src, provider->dataSources()) {
if (ui->telemetrySlider->value() >= telemetryModeIndex(src->collectionMode()))
detailsStr += QStringLiteral("<li>") + src->description() + QStringLiteral("</li>");
}
ui->telemetryDetails->setHtml(detailsStr + QStringLiteral("</ul>"));
ui->telemetryDetails->setHtml(controller->telemetryModeDetails(ui->telemetrySlider->value()));
} else {
QByteArray jsonData;
QMetaObject::invokeMethod(provider, "jsonData", Q_RETURN_ARG(QByteArray, jsonData), Q_ARG(UserFeedback::Provider::StatisticsCollectionMode, telemetryModeMap[ui->telemetrySlider->value()]));
QMetaObject::invokeMethod(controller->feedbackProvider(), "jsonData", Q_RETURN_ARG(QByteArray, jsonData), Q_ARG(UserFeedback::Provider::StatisticsCollectionMode, controller->telemetryIndexToMode(ui->telemetrySlider->value())));
ui->telemetryDetails->setPlainText(QString::fromUtf8(jsonData.constData()));
}
}
void FeedbackConfigWidgetPrivate::surveySliderChanged()
{
if (!provider)
if (!controller->feedbackProvider())
return;
switch (ui->surveySlider->value()) {
......@@ -165,8 +116,10 @@ FeedbackConfigWidget::FeedbackConfigWidget(QWidget* parent)
: QWidget(parent)
, d(new FeedbackConfigWidgetPrivate)
{
d->controller = new FeedbackConfigUiController(this);
d->ui.reset(new Ui::FeedbackConfigWidget);
d->ui->setupUi(this);
d->ui->noTelemetryLabel->setText(d->controller->telemetryModeDescription(Provider::NoStatistics));
connect(d->ui->telemetrySlider, SIGNAL(valueChanged(int)), this, SLOT(telemetrySliderChanged()));
connect(d->ui->telemetrySlider, SIGNAL(valueChanged(int)), this, SIGNAL(configurationChanged()));
......@@ -187,44 +140,24 @@ FeedbackConfigWidget::~FeedbackConfigWidget()
Provider* FeedbackConfigWidget::feedbackProvider() const
{
return d->provider;
return d->controller->feedbackProvider();
}
void FeedbackConfigWidget::setFeedbackProvider(Provider* provider)
{
d->provider = provider;
d->controller->setFeedbackProvider(provider);
if (!provider) {
setEnabled(false);
return;
}
d->telemetryModeMap.clear();
d->telemetryModeMap.reserve(5);
d->telemetryModeMap.push_back(Provider::NoStatistics);
d->telemetryModeMap.push_back(Provider::BasicSystemInformation);
d->telemetryModeMap.push_back(Provider::BasicUsageStatistics);
d->telemetryModeMap.push_back(Provider::DetailedSystemInformation);
d->telemetryModeMap.push_back(Provider::DetailedUsageStatistics);
QSet<Provider::StatisticsCollectionMode> supportedModes;
supportedModes.reserve(d->telemetryModeMap.size());
supportedModes.insert(Provider::NoStatistics);
foreach (const auto &src, provider->dataSources())
supportedModes.insert(src->collectionMode());
for (auto it = d->telemetryModeMap.begin(); it != d->telemetryModeMap.end();) {
if (!supportedModes.contains(*it))
it = d->telemetryModeMap.erase(it);
else
++it;
}
const auto hasTelemetry = d->telemetryModeMap.size() > 1;
const auto hasTelemetry = d->controller->telemetryModeCount() > 1;
d->ui->telemetrySlider->setEnabled(hasTelemetry);
d->ui->telemetryStack->setEnabled(hasTelemetry);
if (hasTelemetry)
d->ui->telemetrySlider->setMaximum(d->telemetryModeMap.size() - 1);
d->ui->telemetrySlider->setMaximum(d->controller->telemetryModeCount() - 1);
d->ui->telemetrySlider->setValue(d->telemetryModeIndex(provider->statisticsCollectionMode()));
d->ui->telemetrySlider->setValue(d->controller->telemetryModeToIndex(provider->statisticsCollectionMode()));
if (provider->surveyInterval() < 0)
d->ui->surveySlider->setValue(0);
......@@ -255,7 +188,7 @@ bool FeedbackConfigWidget::eventFilter(QObject* receiver, QEvent* event)
Provider::StatisticsCollectionMode FeedbackConfigWidget::statisticsCollectionMode() const
{
return d->telemetryModeMap[d->ui->telemetrySlider->value()];
return d->controller->telemetryIndexToMode(d->ui->telemetrySlider->value());
}
int FeedbackConfigWidget::surveyInterval() const
......
......@@ -66,11 +66,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>We make this application for you. You can help us improve it by contributing information on how you use it. This allows us to make sure we focus on things that matter to you.
Contributing statistics is of course entirely anonymous, will not use any kind of unique identifier and will not cover any data you process with this application.</string>
</property>
<widget class="QLabel" name="noTelemetryLabel">
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
......
......@@ -160,6 +160,73 @@ ApplicationWindow {
}
}
UserFeedback.FeedbackConfigUiController {
id: controller
feedbackProvider: provider
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: header.height
Label {
text: qsTr("Contribute Statistics")
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
font.bold: true
}
Slider {
id: telemetrySlider
to: controller.telemetryModeCount - 1
stepSize: 1
value: controller.telemetryModeToIndex(provider.statisticsCollectionMode)
Layout.fillWidth: true
snapMode: Slider.SnapAlways
}
Label {
id: telemetryLabel
text: controller.telemetryModeDescription(telemetrySlider.value)
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
TextArea {
id: telemetryDetails
visible: telemetrySlider.value > 0
text: controller.telemetryModeDetails(telemetrySlider.value)
Layout.fillWidth: true
textFormat: Text.RichText
readOnly: true
}
Label {
text: qsTr("Participate in Surveys")
font.bold: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
Slider {
id: surveySlider
stepSize: 1
to: 2
Layout.fillWidth: true
snapMode: Slider.SnapAlways
}
Label {
id: surveyLabel
text: "TODO"
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
Button {
Layout.alignment: Qt.AlignHCenter
text: telemetrySlider.value + surveySlider.value === 0 ?
qsTr("I don't contriute.") :
qsTr("Contribute!");
onClicked: {
// TODO
stackView.pop();
}
}
}
}
}
......@@ -221,7 +288,7 @@ ApplicationWindow {
text: qsTr("Contribute...")
onClicked: {
encouragementPopup.close();
if (!contributePage.visible)
if (stackView.depth == 1)
stackView.push(contributePage);
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment