diff --git a/src/console/analytics/analyticsview.cpp b/src/console/analytics/analyticsview.cpp index 9a2895c..558bd92 100644 --- a/src/console/analytics/analyticsview.cpp +++ b/src/console/analytics/analyticsview.cpp @@ -1,375 +1,375 @@ /* Copyright (C) 2016 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "analyticsview.h" #include "ui_analyticsview.h" #include "aggregator.h" #include "categoryaggregator.h" #include "chartexportdialog.h" #include "chartutil.h" #include "numericaggregator.h" #include "ratiosetaggregator.h" #include "totalaggregator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KUserFeedback::Console; AnalyticsView::AnalyticsView(QWidget* parent) : QWidget(parent), ui(new Ui::AnalyticsView), m_dataModel(new DataModel(this)), m_timeAggregationModel(new TimeAggregationModel(this)), m_aggregatedDataModel(new AggregatedDataModel(this)), m_nullSingularChart(new QtCharts::QChart), m_nullTimelineChart(new QtCharts::QChart) { ui->setupUi(this); ChartUtil::applyTheme(m_nullSingularChart.get()); ChartUtil::applyTheme(m_nullTimelineChart.get()); ui->singularChartView->setChart(m_nullSingularChart.get()); ui->timelineChartView->setChart(m_nullTimelineChart.get()); ui->dataView->setModel(m_dataModel); ui->aggregatedDataView->setModel(m_aggregatedDataModel); m_timeAggregationModel->setSourceModel(m_dataModel); connect(m_timeAggregationModel, &QAbstractItemModel::modelReset, this, &AnalyticsView::updateTimeSliderRange); ui->actionAggregateYear->setData(TimeAggregationModel::AggregateYear); ui->actionAggregateMonth->setData(TimeAggregationModel::AggregateMonth); ui->actionAggregateWeek->setData(TimeAggregationModel::AggregateWeek); ui->actionAggregateDay->setData(TimeAggregationModel::AggregateDay); auto aggrGroup = new QActionGroup(this); aggrGroup->addAction(ui->actionAggregateYear); aggrGroup->addAction(ui->actionAggregateMonth); aggrGroup->addAction(ui->actionAggregateWeek); aggrGroup->addAction(ui->actionAggregateDay); aggrGroup->setExclusive(true); connect(aggrGroup, &QActionGroup::triggered, this, [this, aggrGroup]() { m_timeAggregationModel->setAggregationMode(static_cast(aggrGroup->checkedAction()->data().toInt())); }); auto timeAggrMenu = new QMenu(tr("&Time Interval"), this); timeAggrMenu->addAction(ui->actionAggregateDay); timeAggrMenu->addAction(ui->actionAggregateWeek); timeAggrMenu->addAction(ui->actionAggregateMonth); timeAggrMenu->addAction(ui->actionAggregateYear); auto chartModeGroup = new QActionGroup(this); chartModeGroup->addAction(ui->actionSingularChart); chartModeGroup->addAction(ui->actionTimelineChart); connect(chartModeGroup, &QActionGroup::triggered, this, &AnalyticsView::updateChart); auto chartMode = new QMenu(tr("&Chart Mode"), this); chartMode->addAction(ui->actionSingularChart); chartMode->addAction(ui->actionTimelineChart); ui->actionReload->setShortcut(QKeySequence::Refresh); connect(ui->actionReload, &QAction::triggered, m_dataModel, &DataModel::reload); connect(ui->actionExportChart, &QAction::triggered, this, &AnalyticsView::exportChart); connect(ui->actionExportData, &QAction::triggered, this, &AnalyticsView::exportData); connect(ui->actionImportData, &QAction::triggered, this, &AnalyticsView::importData); addActions({ timeAggrMenu->menuAction(), chartMode->menuAction(), ui->actionReload, ui->actionExportChart, ui->actionExportData, ui->actionImportData }); QSettings settings; settings.beginGroup(QStringLiteral("Analytics")); const auto aggrSetting = settings.value(QStringLiteral("TimeAggregationMode"), TimeAggregationModel::AggregateMonth).toInt(); foreach (auto act, aggrGroup->actions()) act->setChecked(act->data().toInt() == aggrSetting); m_timeAggregationModel->setAggregationMode(static_cast(aggrSetting)); settings.endGroup(); connect(ui->chartType, QOverload::of(&QComboBox::currentIndexChanged), this, &AnalyticsView::chartSelected); connect(ui->timeSlider, &QSlider::valueChanged, this, [this](int value) { auto aggr = ui->chartType->currentData().value(); if (!aggr) return; aggr->setSingularTime(value); ui->timeLabel->setText(aggr->singularAggregationModel()->index(0, 0).data(TimeAggregationModel::TimeDisplayRole).toString()); }); connect(ui->dataView, &QWidget::customContextMenuRequested, this, [this](QPoint pos) { QMenu menu; menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), [this, pos]() { const auto idx = ui->dataView->indexAt(pos); QGuiApplication::clipboard()->setText(idx.data().toString()); }); menu.exec(ui->dataView->viewport()->mapToGlobal(pos)); }); } AnalyticsView::~AnalyticsView() { QSettings settings; settings.beginGroup(QStringLiteral("Analytics")); settings.setValue(QStringLiteral("TimeAggregationMode"), m_timeAggregationModel->aggregationMode()); settings.endGroup(); // the chart views can't handle null or deleted charts, so set them to something safe ui->singularChartView->setChart(m_nullSingularChart.get()); ui->timelineChartView->setChart(m_nullTimelineChart.get()); qDeleteAll(m_aggregators); } void AnalyticsView::setRESTClient(RESTClient* client) { m_client = client; m_dataModel->setRESTClient(client); } void AnalyticsView::setProduct(const Product& product) { // the chart views can't handle null or deleted charts, so set them to something safe ui->singularChartView->setChart(m_nullSingularChart.get()); ui->timelineChartView->setChart(m_nullTimelineChart.get()); m_dataModel->setProduct(product); ui->chartType->clear(); m_aggregatedDataModel->clear(); qDeleteAll(m_aggregators); m_aggregators.clear(); m_aggregatedDataModel->addSourceModel(m_timeAggregationModel); auto totalsAggr = new TotalAggregator; m_aggregators.push_back(totalsAggr); totalsAggr->setSourceModel(m_timeAggregationModel); ui->chartType->addItem(totalsAggr->displayName(), QVariant::fromValue(totalsAggr)); foreach (const auto &aggr, product.aggregations()) { auto aggregator = createAggregator(aggr); if (!aggregator) continue; m_aggregators.push_back(aggregator); if (auto model = aggregator->timeAggregationModel()) { m_aggregatedDataModel->addSourceModel(model, aggregator->displayName()); } if (aggregator->chartModes() != Aggregator::None) ui->chartType->addItem(aggregator->displayName(), QVariant::fromValue(aggregator)); } } void AnalyticsView::chartSelected() { auto aggr = ui->chartType->currentData().value(); if (!aggr) return; const auto chartMode = aggr->chartModes(); ui->actionSingularChart->setEnabled(chartMode & Aggregator::Singular); ui->actionTimelineChart->setEnabled(chartMode & Aggregator::Timeline); if (chartMode != (Aggregator::Timeline | Aggregator::Singular)) { ui->actionSingularChart->setChecked(chartMode & Aggregator::Singular); ui->actionTimelineChart->setChecked(chartMode & Aggregator::Timeline); } updateChart(); } void AnalyticsView::updateChart() { auto aggr = ui->chartType->currentData().value(); if (!aggr) return; if (ui->actionTimelineChart->isChecked()) { ui->timelineChartView->setChart(aggr->timelineChart()); ui->chartStack->setCurrentWidget(ui->timelinePage); } else if (ui->actionSingularChart->isChecked()) { ui->singularChartView->setChart(aggr->singlularChart()); ui->chartStack->setCurrentWidget(ui->singularPage); aggr->setSingularTime(ui->timeSlider->value()); } } void AnalyticsView::updateTimeSliderRange() { if (m_timeAggregationModel->rowCount() > 0) { ui->timeSlider->setRange(0, m_timeAggregationModel->rowCount() - 1); ui->timeLabel->setText(m_timeAggregationModel->index(ui->timeSlider->value(), 0).data(TimeAggregationModel::TimeDisplayRole).toString()); auto aggr = ui->chartType->currentData().value(); if (aggr) aggr->setSingularTime(ui->timeSlider->value()); } } Aggregator* AnalyticsView::createAggregator(const Aggregation& aggr) const { Aggregator *aggregator = nullptr; switch (aggr.type()) { case Aggregation::None: break; case Aggregation::Category: aggregator = new CategoryAggregator; break; case Aggregation::Numeric: aggregator = new NumericAggregator; break; case Aggregation::RatioSet: aggregator = new RatioSetAggregator; break; } if (!aggregator) return nullptr; aggregator->setAggregation(aggr); aggregator->setSourceModel(m_timeAggregationModel); return aggregator; } void AnalyticsView::exportData() { const auto fileName = QFileDialog::getSaveFileName(this, tr("Export Data")); if (fileName.isEmpty()) return; QFile f(fileName); if (!f.open(QFile::WriteOnly)) { QMessageBox::critical(this, tr("Export Failed"), tr("Could not open file: %1").arg(f.errorString())); return; } const auto samples = m_dataModel->index(0, 0).data(DataModel::AllSamplesRole).value>(); f.write(Sample::toJson(samples, m_dataModel->product())); emit logMessage(tr("Data samples of %1 exported to %2.").arg(m_dataModel->product().name(), f.fileName())); } void AnalyticsView::importData() { const auto fileName = QFileDialog::getOpenFileName(this, tr("Import Data")); if (fileName.isEmpty()) return; QFile f(fileName); if (!f.open(QFile::ReadOnly)) { QMessageBox::critical(this, tr("Import Failed"), tr("Could not open file: %1").arg(f.errorString())); return; } const auto samples = Sample::fromJson(f.readAll(), m_dataModel->product()); if (samples.isEmpty()) { QMessageBox::critical(this, tr("Import Failed"), tr("Selected file contains no valid data.")); return; } auto reply = RESTApi::addSamples(m_client, m_dataModel->product(), samples); connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() == QNetworkReply::NoError) { emit logMessage(tr("Data samples imported.")); m_dataModel->reload(); } }); } void AnalyticsView::exportChart() { ChartExportDialog dlg(this); if (dlg.exec() != QDialog::Accepted) return; QtCharts::QChart *chart = nullptr; QGraphicsScene *scene = nullptr; if (ui->actionTimelineChart->isChecked()) { chart = ui->timelineChartView->chart(); scene = ui->timelineChartView->scene(); } else if (ui->actionSingularChart->isChecked()) { chart = ui->singularChartView->chart(); scene = ui->singularChartView->scene(); } Q_ASSERT(chart); Q_ASSERT(scene); chart->setTheme(QtCharts::QChart::ChartThemeLight); switch (dlg.type()) { case ChartExportDialog::Image: { QImage img(dlg.size(), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter p(&img); - p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setRenderHint(QPainter::Antialiasing); scene->render(&p, QRectF(QPoint(), dlg.size()), scene->sceneRect()); img.save(dlg.filename()); break; } case ChartExportDialog::SVG: { QSvgGenerator svg; svg.setFileName(dlg.filename()); svg.setSize(scene->sceneRect().size().toSize()); svg.setViewBox(scene->sceneRect()); svg.setTitle(ui->chartType->currentText()); QPainter p(&svg); - p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setRenderHint(QPainter::Antialiasing); scene->render(&p); break; } case ChartExportDialog::PDF: { QPdfWriter pdf(dlg.filename()); pdf.setCreator(QStringLiteral("UserFeedbackConsole")); pdf.setTitle(ui->chartType->currentText()); if (scene->sceneRect().width() > scene->sceneRect().height()) pdf.setPageOrientation(QPageLayout::Landscape); QPainter p(&pdf); - p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setRenderHint(QPainter::Antialiasing); scene->render(&p); break; } } ChartUtil::applyTheme(chart); } diff --git a/src/provider/core/propertyratiosource.cpp b/src/provider/core/propertyratiosource.cpp index 0914a81..e2fe951 100644 --- a/src/provider/core/propertyratiosource.cpp +++ b/src/provider/core/propertyratiosource.cpp @@ -1,268 +1,269 @@ /* Copyright (C) 2016 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "propertyratiosource.h" #include "abstractdatasource_p.h" #include "logging_p.h" #include #include #include #include #include #include #include #include #include +#include using namespace KUserFeedback; namespace KUserFeedback { class PropertyRatioSourcePrivate : public AbstractDataSourcePrivate { public: PropertyRatioSourcePrivate(); ~PropertyRatioSourcePrivate(); void propertyChanged(); QString valueToString(const QVariant &value) const; void trySetup(); QString name; QString description; QPointer obj; QByteArray propertyName; QObject *signalMonitor; QMetaProperty property; QString previousValue; - QTime lastChangeTime; + QElapsedTimer lastChangeTime; QHash ratioSet; // data we are currently tracking QHash baseRatioSet; // data loaded from storage QMap valueMap; }; // inefficient workaround for not being able to connect QMetaMethod to a function directly class SignalMonitor : public QObject { Q_OBJECT public: explicit SignalMonitor(PropertyRatioSourcePrivate *r) : m_receiver(r) {} public Q_SLOTS: void propertyChanged() { m_receiver->propertyChanged(); } private: PropertyRatioSourcePrivate *m_receiver; }; } PropertyRatioSourcePrivate::PropertyRatioSourcePrivate() : obj(nullptr) , signalMonitor(nullptr) { } PropertyRatioSourcePrivate::~PropertyRatioSourcePrivate() { delete signalMonitor; } void PropertyRatioSourcePrivate::propertyChanged() { if (!previousValue.isEmpty() && lastChangeTime.elapsed() > 1000) { ratioSet[previousValue] += lastChangeTime.elapsed() / 1000; } lastChangeTime.start(); previousValue = valueToString(property.read(obj)); } QString PropertyRatioSourcePrivate::valueToString(const QVariant &value) const { const auto it = valueMap.constFind(value); if (it != valueMap.constEnd() && it.key() == value) { return it.value(); } return value.toString(); } void PropertyRatioSourcePrivate::trySetup() { if (!obj || propertyName.isEmpty()) return; auto idx = obj->metaObject()->indexOfProperty(propertyName.constData()); Q_ASSERT(idx >= 0); if (idx < 0) { qCWarning(Log) << "Property" << propertyName << "not found in" << obj << "!"; return; } property = obj->metaObject()->property(idx); if (!property.hasNotifySignal()) { qCWarning(Log) << "Property" << propertyName << "has no notification signal!"; return; } idx = signalMonitor->metaObject()->indexOfMethod("propertyChanged()"); Q_ASSERT(idx >= 0); const auto propertyChangedMethod = signalMonitor->metaObject()->method(idx); QObject::connect(obj, property.notifySignal(), signalMonitor, propertyChangedMethod); lastChangeTime.start(); propertyChangedMethod.invoke(signalMonitor, Qt::QueuedConnection); } PropertyRatioSource::PropertyRatioSource(QObject *obj, const char *propertyName, const QString &sampleName) : AbstractDataSource(sampleName, Provider::DetailedUsageStatistics, new PropertyRatioSourcePrivate) { Q_D(PropertyRatioSource); d->obj = obj; d->propertyName = propertyName; d->signalMonitor = new SignalMonitor(d); d->trySetup(); } QObject* PropertyRatioSource::object() const { Q_D(const PropertyRatioSource); return d->obj; } void PropertyRatioSource::setObject(QObject* object) { Q_D(PropertyRatioSource); if (d->obj == object) return; d->obj = object; d->trySetup(); } QString PropertyRatioSource::propertyName() const { Q_D(const PropertyRatioSource); return QString::fromUtf8(d->propertyName.constData()); } void PropertyRatioSource::setPropertyName(const QString& name) { Q_D(PropertyRatioSource); const auto n = name.toUtf8(); if (d->propertyName == n) return; d->propertyName = n; d->trySetup(); } void PropertyRatioSource::addValueMapping(const QVariant &value, const QString &str) { Q_D(PropertyRatioSource); d->valueMap.insert(value, str); } QString PropertyRatioSource::name() const { Q_D(const PropertyRatioSource); return d->name; } void PropertyRatioSource::setName(const QString &name) { Q_D(PropertyRatioSource); d->name = name; } QString PropertyRatioSource::description() const { Q_D(const PropertyRatioSource); return d->description; } void PropertyRatioSource::setDescription(const QString& desc) { Q_D(PropertyRatioSource); d->description = desc; } QVariant PropertyRatioSource::data() { Q_D(PropertyRatioSource); d->propertyChanged(); QVariantMap m; int total = 0; for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) total += it.value() + d->baseRatioSet.value(it.key()); if (total <= 0) return m; for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) { double currentValue = it.value() + d->baseRatioSet.value(it.key()); QVariantMap v; v.insert(QStringLiteral("property"), currentValue / (double)(total)); m.insert(it.key(), v); } return m; } void PropertyRatioSource::loadImpl(QSettings *settings) { Q_D(PropertyRatioSource); foreach (const auto &value, settings->childKeys()) { const auto amount = std::max(settings->value(value, 0).toInt(), 0); d->baseRatioSet.insert(value, amount); if (!d->ratioSet.contains(value)) d->ratioSet.insert(value, 0); } } void PropertyRatioSource::storeImpl(QSettings *settings) { Q_D(PropertyRatioSource); d->propertyChanged(); // note that a second process can have updated the data meanwhile! for (auto it = d->ratioSet.begin(); it != d->ratioSet.end(); ++it) { if (it.value() == 0) continue; const auto oldValue = std::max(settings->value(it.key(), 0).toInt(), 0); const auto newValue = oldValue + it.value(); settings->setValue(it.key(), newValue); *it = 0; d->baseRatioSet.insert(it.key(), newValue); } } void PropertyRatioSource::resetImpl(QSettings* settings) { Q_D(PropertyRatioSource); d->baseRatioSet.clear(); d->ratioSet.clear(); settings->remove(QString()); } #include "propertyratiosource.moc" diff --git a/src/provider/core/provider_p.h b/src/provider/core/provider_p.h index ec8878a..1e61fd5 100644 --- a/src/provider/core/provider_p.h +++ b/src/provider/core/provider_p.h @@ -1,115 +1,116 @@ /* Copyright (C) 2017 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KUSERFEEDBACK_PROVIDER_P_H #define KUSERFEEDBACK_PROVIDER_P_H #include "provider.h" #include #include #include #include #include #include +#include #include QT_BEGIN_NAMESPACE class QNetworkAccessManager; class QNetworkReply; class QSettings; QT_END_NAMESPACE namespace KUserFeedback { class ProviderPrivate : public SurveyTargetExpressionDataProvider { public: explicit ProviderPrivate(Provider *qq); ~ProviderPrivate(); int currentApplicationTime() const; std::unique_ptr makeSettings() const; std::unique_ptr makeGlobalSettings() const; void load(); void store(); void storeOne(const QString &key, const QVariant &value); void storeOneGlobal(const QString &key, const QVariant &value); void aboutToQuit(); bool isValidSource(AbstractDataSource *source) const; QByteArray jsonData(Provider::TelemetryMode mode) const; void scheduleNextSubmission(qint64 minTime = 0); void submitProbe(const QUrl &url); void submitProbeFinished(QNetworkReply *reply); void submit(const QUrl &url); void submitFinished(QNetworkReply *reply); bool selectSurvey(const SurveyInfo &survey) const; Provider::TelemetryMode highestTelemetryMode() const; void scheduleEncouragement(); void emitShowEncouragementMessage(); void writeAuditLog(const QDateTime &dt); QVariant sourceData(const QString &sourceId) const override; Provider *q; QString productId; QTimer submissionTimer; QNetworkAccessManager *networkAccessManager; QUrl serverUrl; QDateTime lastSubmitTime; int redirectCount; int submissionInterval; Provider::TelemetryMode telemetryMode; int surveyInterval; QDateTime lastSurveyTime; QStringList completedSurveys; - QTime startTime; + QElapsedTimer startTime; int startCount; int usageTime; QTimer encouragementTimer; QDateTime lastEncouragementTime; int encouragementStarts; int encouragementTime; int encouragementDelay; int encouragementInterval; int backoffIntervalMinutes; QVector dataSources; QHash dataSourcesById; }; } #endif diff --git a/src/provider/core/selectionratiosource.cpp b/src/provider/core/selectionratiosource.cpp index a21ce98..1784acf 100644 --- a/src/provider/core/selectionratiosource.cpp +++ b/src/provider/core/selectionratiosource.cpp @@ -1,181 +1,182 @@ /* Copyright (C) 2017 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "selectionratiosource.h" #include "abstractdatasource_p.h" #include "logging_p.h" #include #include #include #include #include #include +#include #include using namespace KUserFeedback; namespace KUserFeedback { class SelectionRatioSourcePrivate : public AbstractDataSourcePrivate { public: SelectionRatioSourcePrivate(); ~SelectionRatioSourcePrivate(); void selectionChanged(); QString selectedValue() const; QItemSelectionModel *model; QMetaObject::Connection monitorConnection; QString description; QString previousValue; - QTime lastChangeTime; + QElapsedTimer lastChangeTime; QHash ratioSet; // data we are currently tracking QHash baseRatioSet; // data loaded from storage int role; }; } SelectionRatioSourcePrivate::SelectionRatioSourcePrivate() : model(nullptr) , role(Qt::DisplayRole) { } SelectionRatioSourcePrivate::~SelectionRatioSourcePrivate() { QObject::disconnect(monitorConnection); } void SelectionRatioSourcePrivate::selectionChanged() { if (!previousValue.isEmpty() && lastChangeTime.elapsed() > 1000) { ratioSet[previousValue] += lastChangeTime.elapsed() / 1000; } lastChangeTime.start(); previousValue = selectedValue(); } QString SelectionRatioSourcePrivate::selectedValue() const { const auto idxs = model->selectedIndexes(); if (!model->hasSelection() || idxs.isEmpty()) return QString(); Q_ASSERT(!idxs.isEmpty()); const auto idx = idxs.at(0); return idx.data(role).toString(); } SelectionRatioSource::SelectionRatioSource(QItemSelectionModel* selectionModel, const QString& sampleName) : AbstractDataSource(sampleName, Provider::DetailedUsageStatistics, new SelectionRatioSourcePrivate) { Q_D(SelectionRatioSource); d->model = selectionModel; Q_ASSERT(selectionModel); d->monitorConnection = QObject::connect(selectionModel, &QItemSelectionModel::selectionChanged, [this]() { Q_D(SelectionRatioSource); d->selectionChanged(); }); d->lastChangeTime.start(); d->selectionChanged(); } void SelectionRatioSource::setRole(int role) { Q_D(SelectionRatioSource); d->role = role; } QString SelectionRatioSource::description() const { Q_D(const SelectionRatioSource); return d->description; } void SelectionRatioSource::setDescription(const QString& desc) { Q_D(SelectionRatioSource); d->description = desc; } QVariant SelectionRatioSource::data() { Q_D(SelectionRatioSource); d->selectionChanged(); QVariantMap m; int total = 0; for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) total += it.value() + d->baseRatioSet.value(it.key()); if (total <= 0) return m; for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) { double currentValue = it.value() + d->baseRatioSet.value(it.key()); QVariantMap v; v.insert(QStringLiteral("property"), currentValue / (double)(total)); m.insert(it.key(), v); } return m; } void SelectionRatioSource::loadImpl(QSettings *settings) { Q_D(SelectionRatioSource); foreach (const auto &value, settings->childKeys()) { const auto amount = std::max(settings->value(value, 0).toInt(), 0); d->baseRatioSet.insert(value, amount); if (!d->ratioSet.contains(value)) d->ratioSet.insert(value, 0); } } void SelectionRatioSource::storeImpl(QSettings *settings) { Q_D(SelectionRatioSource); d->selectionChanged(); // note that a second process can have updated the data meanwhile! for (auto it = d->ratioSet.begin(); it != d->ratioSet.end(); ++it) { if (it.value() == 0) continue; const auto oldValue = std::max(settings->value(it.key(), 0).toInt(), 0); const auto newValue = oldValue + it.value(); settings->setValue(it.key(), newValue); *it = 0; d->baseRatioSet.insert(it.key(), newValue); } } void SelectionRatioSource::resetImpl(QSettings* settings) { Q_D(SelectionRatioSource); d->baseRatioSet.clear(); d->ratioSet.clear(); settings->remove(QString()); } diff --git a/src/provider/widgets/styleinfosource.cpp b/src/provider/widgets/styleinfosource.cpp index 34e3e75..927b6e3 100644 --- a/src/provider/widgets/styleinfosource.cpp +++ b/src/provider/widgets/styleinfosource.cpp @@ -1,55 +1,55 @@ /* Copyright (C) 2017 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "styleinfosource.h" #include #include #include #include using namespace KUserFeedback; StyleInfoSource::StyleInfoSource() : AbstractDataSource(QStringLiteral("style")) { } QString StyleInfoSource::description() const { return tr("The widget style used by the application, and information about the used color scheme."); } QVariant StyleInfoSource::data() { QVariantMap m; if (qApp && qApp->style()) m.insert(QStringLiteral("style"), qApp->style()->objectName()); // QStyleFactory sets the object name to the style name - m.insert(QStringLiteral("dark"), qApp->palette().color(QPalette::Background).lightness() < 128); + m.insert(QStringLiteral("dark"), qApp->palette().color(QPalette::Window).lightness() < 128); return m; } QString StyleInfoSource::name() const { return tr("Application style"); }