[weather] Add configuration option which weather services providers to use

Summary:
When searching a weather station/location to select for the weather applet,
so far the user had no chance to control which weather services providers
are queried. Instead simply all are given the search string. Which can both
result in unwanted hits due to regions covered, as well as some minimal
privacy breach.

This patch adds a dropdown menu to the search form where the user can
control which services providers are used. The settings selected is stored
in the config for the given applet instance.

The setting defaults to an empty list, so the user has to opt-in to the use
of any provider.

Ideally the dropdown menu listing the providers with checkboxes would stay
open after toggling a selection, but QtComponents Menu seems to not allow
any modification of that behaviour. This is a small annoyance, but only once
in a while.

Reviewers: #plasma, broulik

Reviewed By: #plasma, broulik

Subscribers: broulik, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D9751
parent 3567fd34
......@@ -29,6 +29,7 @@ set(weather_SRCS
plugin/plugin.cpp
plugin/abstractunitlistmodel.cpp
plugin/locationlistmodel.cpp
plugin/servicelistmodel.cpp
)
add_library(weatherplugin SHARED ${weather_SRCS})
......
/*
* Copyright 2016 Friedrich W. H. Kossebau <kossebau@kde.org>
* Copyright 2016,2018 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 1.0 as QtControls
import QtQuick 2.2
import QtQuick.Controls 1.4 as QtControls
import QtQuick.Layouts 1.0
import org.kde.plasma.components 2.0 as PlasmaComponents
......@@ -27,11 +27,15 @@ import org.kde.plasma.private.weather 1.0
ColumnLayout {
id: generalConfigPage
property alias selectedServices : serviceListModel.selectedServices
signal configurationChanged
function saveConfig() {
var config = {};
config.services = selectedServices;
// only pick a new source if there is one selected in the locationListView
if (locationListView.rowCount && locationListView.currentRow !== -1) {
config.source = locationListModel.valueForListIndex(locationListView.currentRow);
......@@ -49,7 +53,7 @@ ColumnLayout {
locationListView.selection.clear();
noSearchResultReport.visible = false;
locationListModel.searchLocations(searchStringEdit.text);
locationListModel.searchLocations(searchStringEdit.text, selectedServices);
}
function handleLocationSearchDone(success, searchString) {
......@@ -62,6 +66,8 @@ ColumnLayout {
Component.onCompleted: {
var config = plasmoid.nativeInterface.configValues();
selectedServices = config.services;
var source;
var sourceDetails = config.source.split('|');
if (sourceDetails.length > 2) {
......@@ -80,6 +86,32 @@ ColumnLayout {
onLocationSearchDone: handleLocationSearchDone(success, searchString);
}
ServiceListModel {
id: serviceListModel
}
QtControls.Menu {
id: serviceSelectionMenu
Instantiator {
model: serviceListModel
delegate: QtControls.MenuItem {
text: model.display
checkable: true
checked: model.checked
onToggled: {
model.checked = checked;
checked = Qt.binding(function() { return model.checked; });
generalConfigPage.configurationChanged();
}
}
onObjectAdded: serviceSelectionMenu.insertItem(index, object)
onObjectRemoved: serviceSelectionMenu.removeItem(object)
}
}
GridLayout {
columns: 2
......@@ -112,6 +144,13 @@ ColumnLayout {
Layout.fillWidth: true
}
QtControls.Button {
id: serviceSelectionButton
iconName: "services"
tooltip: i18n("Select weather services providers")
menu: serviceSelectionMenu
}
Item {
Layout.preferredHeight: Math.max(searchButton.height, searchStringEdit.height)
Layout.preferredWidth: Layout.preferredHeight
......@@ -126,7 +165,7 @@ ColumnLayout {
QtControls.Button {
id: searchButton
text: i18n("Search")
enabled: !!searchStringEdit.text
enabled: !!searchStringEdit.text && selectedServices.length
onClicked: searchLocation();
}
}
......
......@@ -83,7 +83,7 @@ QString LocationListModel::nameForListIndex(int listIndex) const
return QString();
}
void LocationListModel::searchLocations(const QString &searchString)
void LocationListModel::searchLocations(const QString &searchString, const QStringList& services)
{
m_checkedInCount = 0;
......@@ -113,6 +113,10 @@ void LocationListModel::searchLocations(const QString &searchString)
for (const QVariant& plugin : plugins) {
const QStringList pluginInfo = plugin.toString().split(QLatin1Char('|'));
if (pluginInfo.count() > 1) {
const QString& ionId = pluginInfo[1];
if (!services.contains(ionId)) {
continue;
}
//qDebug() << "ion: " << pluginInfo[0] << pluginInfo[1];
//d->ions.insert(pluginInfo[1], pluginInfo[0]);
......@@ -120,7 +124,7 @@ void LocationListModel::searchLocations(const QString &searchString)
connect(validator, &WeatherValidator::error, this, &LocationListModel::validatorError);
connect(validator, &WeatherValidator::finished, this, &LocationListModel::addSources);
validator->setDataEngine(dataengine);
validator->setIon(pluginInfo[1]);
validator->setIon(ionId);
m_validators.append(validator);
}
......
......@@ -64,7 +64,7 @@ public:
public:
Q_INVOKABLE QString nameForListIndex(int listIndex) const;
Q_INVOKABLE QString valueForListIndex(int listIndex) const;
Q_INVOKABLE void searchLocations(const QString &searchString);
Q_INVOKABLE void searchLocations(const QString &searchString, const QStringList& services);
Q_SIGNALS:
void validatingInputChanged(bool validatingInput);
......
......@@ -19,6 +19,7 @@
#include "abstractunitlistmodel.h"
#include "locationlistmodel.h"
#include "servicelistmodel.h"
#include <KLocalizedString>
......@@ -97,4 +98,5 @@ void WeatherPlugin::registerTypes(const char *uri)
qmlRegisterSingletonType<AbstractUnitListModel>(uri, 1, 0, "VisibilityUnitListModel",
visibilityUnitListModelSingletonTypeProvider);
qmlRegisterType<LocationListModel>(uri, 1, 0, "LocationListModel");
qmlRegisterType<ServiceListModel>(uri, 1, 0, "ServiceListModel");
}
/*
* Copyright 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 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 "servicelistmodel.h"
#include <Plasma/DataContainer>
#include <Plasma/DataEngine>
#include <KLocalizedString>
ServiceListModel::ServiceListModel(QObject *parent)
: QAbstractListModel(parent)
{
Plasma::DataEngine* dataengine = dataEngine(QStringLiteral("weather"));
const QVariantList plugins = dataengine->containerForSource(QLatin1String("ions"))->data().values();
for (const QVariant& plugin : plugins) {
const QStringList pluginInfo = plugin.toString().split(QLatin1Char('|'));
if (pluginInfo.count() > 1) {
m_services.append(ServiceItem(pluginInfo[0], pluginInfo[1]));
}
}
}
int ServiceListModel::rowCount(const QModelIndex &index) const
{
if (!index.isValid()) {
return m_services.size();
}
return 0;
}
QHash<int, QByteArray> ServiceListModel::roleNames() const
{
auto roleNames = QAbstractListModel::roleNames();
roleNames.insert(Qt::CheckStateRole, "checked");
return roleNames;
}
QVariant ServiceListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= m_services.size()) {
return QVariant();
}
const ServiceItem& item = m_services.at(index.row());
switch (role) {
case Qt::DisplayRole: {
return i18nc("weather services provider name (id)",
"%1 (%2)", item.displayName, item.id);
case Qt::CheckStateRole:
return item.checked;
}
}
return QVariant();
}
bool ServiceListModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || value.isNull()) {
return false;
}
if (role == Qt::CheckStateRole) {
ServiceItem& item = m_services[index.row()];
const bool checked = value.toBool();
if (checked == item.checked) {
return true;
}
item.checked = checked;
emit dataChanged(index, index);
if (checked) {
m_selectedServices.append(item.id);
} else {
m_selectedServices.removeAll(item.id);
}
emit selectedServicesChanged();
return true;
}
return false;
}
void ServiceListModel::setSelectedServices(const QStringList& selectedServices)
{
if (m_selectedServices == selectedServices) {
return;
}
m_selectedServices = selectedServices;
for (int i = 0, size = m_services.size(); i < size; ++i) {
ServiceItem& item = m_services[i];
const bool checked = m_selectedServices.contains(item.id);
if (checked == item.checked) {
continue;
}
item.checked = checked;
const QModelIndex index = createIndex(i, 0);
emit dataChanged(index, index);
}
emit selectedServicesChanged();
}
/*
* Copyright 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 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 SERVICELISTMODEL_H
#define SERVICELISTMODEL_H
#include <Plasma/DataEngineConsumer>
#include <QAbstractListModel>
#include <QVector>
class ServiceItem
{
public:
ServiceItem() {}
ServiceItem(const QString& displayName, const QString& id)
: displayName(displayName)
, id(id)
{}
QString displayName;
QString id;
bool checked = false;
};
Q_DECLARE_METATYPE(ServiceItem)
Q_DECLARE_TYPEINFO(ServiceItem, Q_MOVABLE_TYPE);
class ServiceListModel : public QAbstractListModel, public Plasma::DataEngineConsumer
{
Q_OBJECT
Q_PROPERTY(QStringList selectedServices MEMBER m_selectedServices WRITE setSelectedServices NOTIFY selectedServicesChanged)
public:
explicit ServiceListModel(QObject* parent = nullptr);
public: // QAbstractListModel API
QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
int rowCount(const QModelIndex& index) const override;
QHash<int, QByteArray> roleNames() const override;
public:
void setSelectedServices(const QStringList& selectedServices);
Q_SIGNALS:
void selectedServicesChanged();
private:
QStringList m_selectedServices;
QVector<ServiceItem> m_services;
};
#endif // SERVICELISTMODEL_H
......@@ -23,6 +23,7 @@
#include <KLocalizedString>
#include <KIconLoader>
#include <KConfigGroup>
#include <KUnitConversion/Value>
#include <Plasma/Package>
......@@ -37,6 +38,12 @@ T clampValue(T value, int decimals)
}
namespace {
namespace AppletConfigKeys {
inline QString services() { return QStringLiteral("services"); }
}
namespace StorageConfigKeys {
const char weatherServiceProviders[] = "weatherServiceProviders";
}
namespace PanelModelKeys {
inline QString location() { return QStringLiteral("location"); }
inline QString currentDayLowTemperature() { return QStringLiteral("currentDayLowTemperature"); }
......@@ -447,6 +454,16 @@ void WeatherApplet::dataUpdated(const QString &source, const Plasma::DataEngine:
emit modelUpdated();
}
QVariantMap WeatherApplet::configValues() const
{
QVariantMap config = WeatherPopupApplet::configValues();
KConfigGroup cfg = this->config();
config.insert(AppletConfigKeys::services(), cfg.readEntry(StorageConfigKeys::weatherServiceProviders, QStringList()));
return config;
}
void WeatherApplet::saveConfig(const QVariantMap& configChanges)
{
// TODO: if just units where changed there is no need to reset the complete model or reconnect to engine
......@@ -456,6 +473,13 @@ void WeatherApplet::saveConfig(const QVariantMap& configChanges)
emit modelUpdated();
KConfigGroup cfg = config();
auto it = configChanges.find(AppletConfigKeys::services());
if (it != configChanges.end()) {
cfg.writeEntry(StorageConfigKeys::weatherServiceProviders, it.value().toStringList());
}
WeatherPopupApplet::saveConfig(configChanges);
}
......
......@@ -48,6 +48,7 @@ Q_SIGNALS:
void modelUpdated();
public: // WeatherPopupApplet API
QVariantMap configValues() const override;
void saveConfig(const QVariantMap& configChanges) override;
void dataUpdated(const QString &source, const Plasma::DataEngine::Data &data) override;
......
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