Commit ed9b040d authored by Volker Krause's avatar Volker Krause
Browse files

Add new configuration widget

This provides more information on what to share, and allows a more
gradual configuration than the previously used checkboxes. Also, this
is now available for embedding too, not just as a dialog.
parent be07170e
......@@ -21,6 +21,8 @@
#include "userfeedbackcore_export.h"
#include "provider.h"
#include <QCoreApplication>
class QSettings;
namespace UserFeedback {
......@@ -36,6 +38,11 @@ public:
/** Returns the name of this data source. */
QString name() const;
/** Returns a human-readable, translated description of what
* this source provides.
*/
virtual QString description() const = 0;
/** Implement this to return the data gathered by this source.
* Depending on the schema entry type for this source, the following
* formats are expected:
......
......@@ -27,6 +27,11 @@ ApplicationVersionSource::ApplicationVersionSource() :
{
}
QString ApplicationVersionSource::description() const
{
return tr("The version of the application.");
}
QVariant ApplicationVersionSource::data()
{
if (QCoreApplication::applicationVersion().isEmpty())
......
......@@ -25,10 +25,11 @@ namespace UserFeedback {
class USERFEEDBACKCORE_EXPORT ApplicationVersionSource : public AbstractDataSource
{
Q_DECLARE_TR_FUNCTIONS(ApplicationVersionSource)
public:
ApplicationVersionSource();
QVariant data() Q_DECL_OVERRIDE;
QString description() const override;
QVariant data() override;
};
}
......
......@@ -27,6 +27,11 @@ PlatformInfoSource::PlatformInfoSource() :
{
}
QString PlatformInfoSource::description() const
{
return tr("Type and version of the operating system.");
}
QVariant PlatformInfoSource::data()
{
QVariantMap m;
......
......@@ -29,9 +29,11 @@ namespace UserFeedback {
*/
class USERFEEDBACKCORE_EXPORT PlatformInfoSource : public AbstractDataSource
{
Q_DECLARE_TR_FUNCTIONS(PlatformInfoSource)
public:
PlatformInfoSource();
QVariant data() Q_DECL_OVERRIDE;
QString description() const override;
QVariant data() override;
};
}
......
......@@ -124,6 +124,11 @@ void PropertyRatioSource::addValueMapping(const QVariant &value, const QString &
d->valueMap.insert(value, str);
}
QString PropertyRatioSource::description() const
{
return QString(); // TODO
}
QVariant PropertyRatioSource::data()
{
Q_D(PropertyRatioSource);
......
......@@ -49,9 +49,10 @@ public:
*/
void addValueMapping(const QVariant &value, const QString &str);
QVariant data() Q_DECL_OVERRIDE;
void load(QSettings *settings) Q_DECL_OVERRIDE;
void store(QSettings *settings) Q_DECL_OVERRIDE;
QString description() const override;
QVariant data() override;
void load(QSettings *settings) override;
void store(QSettings *settings) override;
private:
Q_DECLARE_PRIVATE(PropertyRatioSource)
......
......@@ -332,6 +332,7 @@ void Provider::setStatisticsCollectionMode(StatisticsCollectionMode mode)
d->statisticsMode = mode;
d->scheduleNextSubmission();
d->scheduleEncouragement();
d->store();
}
void Provider::addDataSource(AbstractDataSource *source, StatisticsCollectionMode mode)
......@@ -345,6 +346,11 @@ void Provider::addDataSource(AbstractDataSource *source, StatisticsCollectionMod
source->load(&settings);
}
QVector<AbstractDataSource*> Provider::dataSources() const
{
return d->dataSources;
}
int Provider::surveyInterval() const
{
return d->surveyInterval;
......@@ -355,6 +361,7 @@ void Provider::setSurveyInterval(int days)
d->surveyInterval = days;
d->scheduleNextSubmission();
d->scheduleEncouragement();
d->store();
}
void Provider::setApplicationStartsUntilEncouragement(int starts)
......
......@@ -38,7 +38,8 @@ public:
enum StatisticsCollectionMode {
NoStatistics,
BasicStatistics,
AllStatistics
AllStatistics,
CollectionModeCount = AllStatistics
};
Q_ENUMS(StatisticsCollectionMode)
......@@ -70,6 +71,11 @@ public:
*/
void addDataSource(AbstractDataSource *source, StatisticsCollectionMode mode);
/** Returns all data sources that have been added to this provider.
* @see addDataSource
*/
QVector<AbstractDataSource*> dataSources() const;
/** Returns the minimum time between two surveys in days. */
int surveyInterval() const;
......@@ -111,6 +117,8 @@ private:
Q_PRIVATE_SLOT(d, void aboutToQuit())
Q_PRIVATE_SLOT(d, void submitFinished())
Q_PRIVATE_SLOT(d, void emitShowEncouragementMessage())
// for UI
Q_PRIVATE_SLOT(d, QByteArray jsonData())
// for testing
Q_PRIVATE_SLOT(d, void load())
Q_PRIVATE_SLOT(d, void store())
......
......@@ -26,6 +26,11 @@ QtVersionSource::QtVersionSource() :
{
}
QString QtVersionSource::description() const
{
return tr("The Qt version used by this application.");
}
QVariant QtVersionSource::data()
{
QVariantMap m;
......
......@@ -26,9 +26,11 @@ namespace UserFeedback {
/*! Data source reporting the Qt version used at runtime. */
class USERFEEDBACKCORE_EXPORT QtVersionSource : public AbstractDataSource
{
Q_DECLARE_TR_FUNCTIONS(QtVersionSource)
public:
QtVersionSource();
QVariant data() Q_DECL_OVERRIDE;
QString description() const override;
QVariant data() override;
};
}
......
......@@ -30,6 +30,11 @@ ScreenInfoSource::ScreenInfoSource() :
{
}
QString ScreenInfoSource::description() const
{
return tr("Size and resolution of all connected screens.");
}
QVariant ScreenInfoSource::data()
{
QVariantList l;
......
......@@ -25,10 +25,11 @@ namespace UserFeedback {
class USERFEEDBACKCORE_EXPORT ScreenInfoSource : public AbstractDataSource
{
Q_DECLARE_TR_FUNCTIONS(ScreenInfoSource)
public:
ScreenInfoSource();
QVariant data() Q_DECL_OVERRIDE;
QString description() const override;
QVariant data() override;
};
}
......
set(userfeedback_widgets_srcs
feedbackconfigdialog.cpp
feedbackconfigwidget.cpp
notificationwidget.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 "feedbackconfigwidget.h"
#include "ui_feedbackconfigwidget.h"
#include <provider/core/abstractdatasource.h>
#include <provider/core/provider.h>
#include <QApplication>
#include <QDebug>
#include <QScrollBar>
#include <algorithm>
#include <vector>
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;
std::unique_ptr<Ui::FeedbackConfigWidget> ui;
std::vector<Provider::StatisticsCollectionMode> telemetryModeMap;
};
}
FeedbackConfigWidgetPrivate::FeedbackConfigWidgetPrivate()
: provider(nullptr)
{
telemetryModeMap.reserve(Provider::CollectionModeCount);
}
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::BasicStatistics:
ui->telemetryLabel->setText(FeedbackConfigWidget::tr(
"Share statistics on how often you use the application. "
"No unique identification is included, nor data processed with the application."
));
break;
// TODO
// case Provider::PlatformInformation:
// ui->telemetryLabel->setText(FeedbackConfigWidget::tr("Basic usage stats and system information TODO"));
// break;
case Provider::AllStatistics:
ui->telemetryLabel->setText(FeedbackConfigWidget::tr(
"Share statistics on how often individual features of the application are used. "
"No unique identification is included, nor data processed with the application."
));
break;
}
applyPalette(ui->telemetrySlider);
if (!provider)
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>"));
} else {
QByteArray jsonData;
QMetaObject::invokeMethod(provider, "jsonData", Q_RETURN_ARG(QByteArray, jsonData));
ui->telemetryDetails->setPlainText(QString::fromUtf8(jsonData));
}
provider->setStatisticsCollectionMode(telemetryModeMap[ui->telemetrySlider->value()]);
}
void FeedbackConfigWidgetPrivate::surveySliderChanged()
{
if (!provider)
return;
switch (ui->surveySlider->value()) {
case 0:
ui->surveyLabel->setText(FeedbackConfigWidget::tr("Encourage to enable surveys text TODO"));
provider->setSurveyInterval(-1);
break;
case 1:
ui->surveyLabel->setText(FeedbackConfigWidget::tr(
"I will occasionally participate in web surveys about the application, not more than four times a year though."
));
provider->setSurveyInterval(90);
break;
case 2:
ui->surveyLabel->setText(FeedbackConfigWidget::tr(
"I will participate in web surveys whenever one is available. Surveys can of course be defered or skipped."
));
provider->setSurveyInterval(0);
break;
}
applyPalette(ui->surveySlider);
}
void FeedbackConfigWidgetPrivate::applyPalette(QSlider* slider)
{
const auto ratio = (double)slider->value() / (double)slider->maximum();
const auto red = qBound<double>(0.0, 2.0 - ratio * 2.0, 1.0);
const auto green = qBound<double>(0.0, ratio * 2.0, 1.0);
auto color = QColor(255 * red, 255 * green, 0);
if (QApplication::palette().color(QPalette::Base).lightness() > 128)
color = color.lighter(150);
else
color = color.darker(150);
auto pal = slider->palette();
pal.setColor(QPalette::Highlight, color);
slider->setPalette(pal);
}
FeedbackConfigWidget::FeedbackConfigWidget(QWidget* parent)
: QWidget(parent)
, d(new FeedbackConfigWidgetPrivate)
{
d->ui.reset(new Ui::FeedbackConfigWidget);
d->ui->setupUi(this);
connect(d->ui->telemetrySlider, SIGNAL(valueChanged(int)), this, SLOT(telemetrySliderChanged()));
connect(d->ui->surveySlider, SIGNAL(valueChanged(int)), this, SLOT(surveySliderChanged()));
d->ui->rawTelemetryButton->setParent(d->ui->telemetryDetails);
d->ui->rawTelemetryButton->setIcon(style()->standardPixmap(QStyle::SP_DialogHelpButton));
d->ui->telemetryDetails->installEventFilter(this);
connect(d->ui->rawTelemetryButton, SIGNAL(toggled(bool)), this, SLOT(telemetrySliderChanged()));
setEnabled(false); // see setFeedbackProvider
}
FeedbackConfigWidget::~FeedbackConfigWidget()
{
}
void FeedbackConfigWidget::setFeedbackProvider(UserFeedback::Provider* provider)
{
d->provider = provider;
if (!provider) {
setEnabled(false);
return;
}
d->telemetryModeMap.clear();
d->telemetryModeMap.push_back(Provider::NoStatistics);
d->telemetryModeMap.push_back(Provider::BasicStatistics);
// TODO
d->telemetryModeMap.push_back(Provider::AllStatistics);
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;
d->ui->telemetrySlider->setEnabled(hasTelemetry);
d->ui->telemetryStack->setEnabled(hasTelemetry);
if (hasTelemetry)
d->ui->telemetrySlider->setMaximum(d->telemetryModeMap.size() - 1);
d->ui->telemetrySlider->setValue(d->telemetryModeIndex(provider->statisticsCollectionMode()));
if (provider->surveyInterval() < 0)
d->ui->surveySlider->setValue(0);
else if (provider->surveyInterval() >= 90)
d->ui->surveySlider->setValue(1);
else
d->ui->surveySlider->setValue(2);
setEnabled(provider);
}
bool FeedbackConfigWidget::eventFilter(QObject* receiver, QEvent* event)
{
if (receiver == d->ui->telemetryDetails) {
d->ui->rawTelemetryButton->move(
d->ui->telemetryDetails->width()
- d->ui->rawTelemetryButton->width()
- style()->pixelMetric(QStyle::PM_LayoutRightMargin)
- (d->ui->telemetryDetails->verticalScrollBar()->isVisible() ? d->ui->telemetryDetails->verticalScrollBar()->width() : 0),
d->ui->telemetryDetails->height()
- d->ui->rawTelemetryButton->height()
- style()->pixelMetric(QStyle::PM_LayoutBottomMargin)
);
}
return QWidget::eventFilter(receiver, event);
}
#include "moc_feedbackconfigwidget.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/>.
*/
#ifndef USERFEEDBACK_FEEDBACKCONFIGWIDGET_H
#define USERFEEDBACK_FEEDBACKCONFIGWIDGET_H
#include "userfeedbackwidgets_export.h"
#include <QWidget>
#include <memory>
namespace UserFeedback {
class FeedbackConfigWidgetPrivate;
class Provider;
/**
* Configuration widget for telemetry and survey contributions.
*
* @see FeedbackConfigDialog
*/
class USERFEEDBACKWIDGETS_EXPORT FeedbackConfigWidget : public QWidget
{
Q_OBJECT
public:
explicit FeedbackConfigWidget(QWidget *parent = nullptr);
~FeedbackConfigWidget();
/** Set the feedback provider that should be configured with this widget. */
void setFeedbackProvider(UserFeedback::Provider *provider);
protected:
bool eventFilter(QObject *receiver, QEvent *event) override;
private:
Q_PRIVATE_SLOT(d, void telemetrySliderChanged())
Q_PRIVATE_SLOT(d, void surveySliderChanged())
std::unique_ptr<FeedbackConfigWidgetPrivate> d;
};
}
#endif // USERFEEDBACK_FEEDBACKCONFIGWIDGET_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UserFeedback::FeedbackConfigWidget</class>
<widget class="QWidget" name="UserFeedback::FeedbackConfigWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Contribute Telemetry</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="telemetrySlider">
<property name="maximum">
<number>3</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="telemetryStack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="noTelemetryPage">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>TODO: encourage to contribute telemetry</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">