diff --git a/applets/weather/package/contents/ui/main.qml b/applets/weather/package/contents/ui/main.qml index c58fea59d..554c80d6a 100644 --- a/applets/weather/package/contents/ui/main.qml +++ b/applets/weather/package/contents/ui/main.qml @@ -1,433 +1,446 @@ /* * Copyright 2018 Friedrich W. H. Kossebau * * 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 . */ import QtQuick 2.9 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.private.weather 1.0 Item { id: root readonly property string weatherSource: plasmoid.nativeInterface.source readonly property int updateInterval: plasmoid.nativeInterface.updateInterval readonly property int displayTemperatureUnit: plasmoid.nativeInterface.displayTemperatureUnit readonly property int displaySpeedUnit: plasmoid.nativeInterface.displaySpeedUnit readonly property int displayPressureUnit: plasmoid.nativeInterface.displayPressureUnit readonly property int displayVisibilityUnit: plasmoid.nativeInterface.displayVisibilityUnit property bool connectingToSource: false readonly property bool needsConfiguration: !generalModel.location && !connectingToSource readonly property int invalidUnit: -1 //TODO: make KUnitConversion::InvalidUnit usable here // model providing final display strings for observation properties readonly property var observationModel: { var model = {}; - var data = weatherDataSource.currentData; + var data = weatherDataSource.currentData || {}; - var reportTemperatureUnit = (data && data["Temperature Unit"]) || invalidUnit; - var reportPressureUnit = (data && data["Pressure Unit"]) || invalidUnit; - var reportVisibilityUnit = (data && data["Visibility Unit"]) || invalidUnit; - var reportWindSpeedUnit = (data && data["Wind Speed Unit"]) || invalidUnit; + function getNumber(key) { + var number = data[key]; + if (typeof number === "string") { + var parsedNumber = parseFloat(number); + return isNaN(parsedNumber) ? null : parsedNumber; + } + return (typeof number !== "undefined") && (number !== "") ? number : null; + } + function getNumberOrString(key) { + var number = data[key]; + return (typeof number !== "undefined") && (number !== "") ? number : null; + } + + var reportTemperatureUnit = data["Temperature Unit"] || invalidUnit; + var reportPressureUnit = data["Pressure Unit"] || invalidUnit; + var reportVisibilityUnit = data["Visibility Unit"] || invalidUnit; + var reportWindSpeedUnit = data["Wind Speed Unit"] || invalidUnit; - model["conditions"] = (data && data["Current Conditions"]) || ""; + model["conditions"] = data["Current Conditions"] || ""; - var conditionIconName = (data && data["Condition Icon"]) || null; + var conditionIconName = data["Condition Icon"] || null; model["conditionIconName"] = conditionIconName ? Util.existingWeatherIconName(conditionIconName) : "weather-none-available"; - var temperature = (data && data["Temperature"]) || null; + var temperature = getNumber("Temperature"); model["temperature"] = temperature !== null ? Util.temperatureToDisplayString(displayTemperatureUnit, temperature, reportTemperatureUnit) : ""; - var windchill = (data && data["Windchill"]) || null; + var windchill = getNumber("Windchill"); // Use temperature unit to convert windchill temperature // we only show degrees symbol not actual temperature unit model["windchill"] = windchill !== null ? Util.temperatureToDisplayString(displayTemperatureUnit, windchill, reportTemperatureUnit, false, true) : ""; - var humidex = (data && data["Humidex"]) || null; + var humidex = getNumber("Humidex"); // TODO: this seems wrong, does the humidex have temperature as units? // Use temperature unit to convert humidex temperature // we only show degrees symbol not actual temperature unit model["humidex"] = humidex !== null ? Util.temperatureToDisplayString(displayTemperatureUnit, humidex, reportTemperatureUnit, false, true) : ""; - var dewpoint = (data && data["Dewpoint"]) || null; + var dewpoint = getNumber("Dewpoint"); model["dewpoint"] = dewpoint !== null ? Util.temperatureToDisplayString(displayTemperatureUnit, dewpoint, reportTemperatureUnit) : ""; - var pressure = (data && data["Pressure"]) || null; - model["pressure"] = pressure ? + var pressure = getNumber("Pressure"); + model["pressure"] = pressure !== null ? Util.valueToDisplayString(displayPressureUnit, pressure, reportPressureUnit, 2) : ""; var pressureTendency = (data && data["Pressure Tendency"]) || null; model["pressureTendency"] = pressureTendency ? i18nc("pressure tendency", pressureTendency) : ""; - var visibility = (data && data["Visibility"]) || null; - model["visibility"] = visibility ? + var visibility = getNumberOrString("Visibility"); + model["visibility"] = visibility !== null ? ((reportVisibilityUnit !== invalidUnit) ? Util.valueToDisplayString(displayVisibilityUnit, visibility, reportVisibilityUnit, 1) : visibility) : ""; - var humidity = (data && data["Humidity"]) || null; - model["humidity"] = humidity ? Util.percentToDisplayString(humidity) : ""; + var humidity = getNumber("Humidity"); + model["humidity"] = humidity !== null ? Util.percentToDisplayString(humidity) : ""; // TODO: missing check for windDirection validness - var windDirection = (data && data["Wind Direction"]) || null; - var windSpeed = (data && data["Wind Speed"]) || null; + var windDirection = data["Wind Direction"] || ""; + var windSpeed = getNumberOrString("Wind Speed"); var windSpeedText; if (windSpeed !== null && windSpeed !== "") { var windSpeedNumeric = (typeof windSpeed !== 'number') ? parseFloat(windSpeed) : windSpeed; if (!isNaN(windSpeedNumeric)) { if (windSpeedNumeric !== 0) { windSpeedText = Util.valueToDisplayString(displaySpeedUnit, windSpeedNumeric, reportWindSpeedUnit, 1); } else { windSpeedText = i18nc("Wind condition", "Calm"); } } else { // TODO: i18n? windSpeedText = windSpeed; } } model["windSpeed"] = windSpeedText || ""; - model["windDirectionId"] = windDirection || ""; + model["windDirectionId"] = windDirection; model["windDirection"] = windDirection ? i18nc("wind direction", windDirection) : ""; - var windGust = (data && data["Wind Gust"]) || null; - model["windGust"] = windGust ? Util.valueToDisplayString(displaySpeedUnit, windGust, reportWindSpeedUnit, 1) : ""; + var windGust = getNumber("Wind Gust"); + model["windGust"] = windGust !== null ? Util.valueToDisplayString(displaySpeedUnit, windGust, reportWindSpeedUnit, 1) : ""; return model; } readonly property var generalModel: { var model = {}; - var data = weatherDataSource.currentData; + var data = weatherDataSource.currentData || {}; - var todayForecastTokens = ((data && data["Short Forecast Day 0"]) || "").split("|"); + var todayForecastTokens = (data["Short Forecast Day 0"] || "").split("|"); - model["location"] = (data && data["Place"]) || ""; - model["courtesy"] = (data && data["Credit"]) || ""; - model["creditUrl"] = (data && data["Credit Url"]) || ""; + model["location"] = data["Place"] || ""; + model["courtesy"] = data["Credit"] || ""; + model["creditUrl"] = data["Credit Url"] || ""; - var forecastDayCount = parseInt((data && data["Total Weather Days"]) || ""); + var forecastDayCount = parseInt(data["Total Weather Days"] || ""); var forecastTitle; if (!isNaN(forecastDayCount) && forecastDayCount > 0) { forecastTitle = i18ncp("Forecast period timeframe", "1 Day", "%1 Days", forecastDayCount); } model["forecastTitle"] = forecastTitle || ""; var conditionIconName = observationModel.conditionIconName; if (!conditionIconName || conditionIconName === "weather-none-available") { // try icon from current weather forecast if (todayForecastTokens.length === 6 && todayForecastTokens[1] !== "N/U") { conditionIconName = Util.existingWeatherIconName(todayForecastTokens[1]); } else { conditionIconName = "weather-none-available"; } } model["currentConditionIconName"] = conditionIconName; return model; } readonly property var detailsModel: { var model = []; if (observationModel.windchill) { model.push({ "label": i18nc("@label", "Windchill:"), "text": observationModel.windchill }); }; if (observationModel.humidex) { model.push({ "label": i18nc("@label", "Humidex:"), "text": observationModel.humidex }); } if (observationModel.dewpoint) { model.push({ "label": i18nc("@label ground temperature", "Dewpoint:"), "text": observationModel.dewpoint }); } if (observationModel.pressure) { model.push({ "label": i18nc("@label", "Pressure:"), "text": observationModel.pressure }); } if (observationModel.pressureTendency) { model.push({ "label": i18nc("@label pressure tendency, rising/falling/steady", "Pressure Tendency:"), "text": observationModel.pressureTendency }); } if (observationModel.visibility) { model.push({ "label": i18nc("@label", "Visibility:"), "text": observationModel.visibility }); } if (observationModel.humidity) { model.push({ "label": i18nc("@label", "Humidity:"), "text": observationModel.humidity }); } if (observationModel.windGust) { model.push({ "label": i18nc("@label", "Wind Gust:"), "text": observationModel.windGust }); } return model; } readonly property var forecastModel: { var model = []; var data = weatherDataSource.currentData; var forecastDayCount = parseInt((data && data["Total Weather Days"]) || ""); if (isNaN(forecastDayCount) || forecastDayCount <= 0) { return model; } var reportTemperatureUnit = (data && data["Temperature Unit"]) || invalidUnit; var dayItems = []; var conditionItems = []; var hiItems = []; var lowItems = []; for (var i = 0; i < forecastDayCount; ++i) { var forecastDayKey = "Short Forecast Day " + i; var forecastDayTokens = ((data && data[forecastDayKey]) || "").split("|"); if (forecastDayTokens.length !== 6) { // We don't have the right number of tokens, abort trying break; } dayItems.push(forecastDayTokens[0]); // If we see N/U (Not Used) we skip the item var weatherIconName = forecastDayTokens[1]; if (weatherIconName && weatherIconName !== "N/U") { var iconAndToolTip = Util.existingWeatherIconName(weatherIconName); iconAndToolTip += "|"; var condition = forecastDayTokens[2]; var probability = forecastDayTokens[5]; if (probability !== "N/U" && probability !== "N/A" && !!probability) { iconAndToolTip += i18nc("certain weather condition (probability percentage)", "%1 (%2 %)", condition, probability); } else { iconAndToolTip += condition; } conditionItems.push(iconAndToolTip); } var tempHigh = forecastDayTokens[3]; if (tempHigh !== "N/U") { if (tempHigh === "N/A" || !tempHigh) { hiItems.push(i18nc("Short for no data available", "-")); } else { hiItems.push(Util.temperatureToDisplayString(displayTemperatureUnit, tempHigh, reportTemperatureUnit, true)); } } var tempLow = forecastDayTokens[4]; if (tempLow !== "N/U") { if (tempLow === "N/A" || !tempLow) { lowItems.push(i18nc("Short for no data available", "-")); } else { lowItems.push(Util.temperatureToDisplayString(displayTemperatureUnit, tempLow, reportTemperatureUnit, true)); } } } if (dayItems.length) { model.push(dayItems); } if (conditionItems.length) { model.push(conditionItems); } if (hiItems.length) { model.push(hiItems); } if (lowItems.length) { model.push(lowItems); } return model; } readonly property var noticesModel: { var model = []; var data = weatherDataSource.currentData; var warnings = []; var warningsCount = parseInt((data && data["Total Warnings Issued"]) || ""); if (isNaN(warningsCount)) { warningsCount = 0; } for (var i = 0; i < warningsCount; ++i) { warnings.push({ "description": data["Warning Description "+i], "info": data["Warning Info "+i] }); } model.push(warnings); var watches = []; var watchesCount = parseInt((data && data["Total Watches Issued"]) || ""); if (isNaN(watchesCount)) { watchesCount = 0; } for (var i = 0; i < watchesCount; ++i) { watches.push({ "description": data["Watch Description "+i], "info": data["Watch Info "+i] }); } model.push(watches); return model; } PlasmaCore.DataSource { id: weatherDataSource readonly property var currentData: data[weatherSource] engine: "weather" connectedSources: weatherSource interval: updateInterval * 60 * 1000 onConnectedSourcesChanged: { if (weatherSource) { connectingToSource = true; plasmoid.busy = true; connectionTimeoutTimer.start(); } } onCurrentDataChanged: { if (currentData) { connectionTimeoutTimer.stop(); connectingToSource = false; plasmoid.busy = false; } } } Timer { id: connectionTimeoutTimer interval: 60 * 1000 // 1 min repeat: false onTriggered: { connectingToSource = false; plasmoid.busy = false; // TODO: inform user var sourceTokens = weatherSource.split("|"); var foo = i18n("Weather information retrieval for %1 timed out.", sourceTokens.value(2)); } } // workaround for now to ensure "Please configure" tooltip // TODO: remove when configurationRequired works Plasmoid.icon: needsConfiguration ? "configure" : generalModel.currentConditionIconName Plasmoid.toolTipMainText: needsConfiguration ? i18nc("@info:tooltip", "Please configure") : generalModel.location Plasmoid.toolTipSubText: { if (!generalModel.location) { return ""; } var tooltips = []; var temperature = plasmoid.nativeInterface.temperatureShownInTooltip ? observationModel.temperature : null; if (observationModel.conditions && temperature) { tooltips.push(i18nc("weather condition + temperature", "%1 %2", observationModel.conditions, temperature)); } else if (observationModel.conditions || temperature) { tooltips.push(observationModel.conditions || temperature); } if (plasmoid.nativeInterface.windShownInTooltip && observationModel.windSpeed) { if (observationModel.windDirection) { if (observationModel.windGust) { tooltips.push(i18nc("winddirection windspeed (windgust)", "%1 %2 (%3)", observationModel.windDirection, observationModel.windSpeed, observationModel.windGust)); } else { tooltips.push(i18nc("winddirection windspeed", "%1 %2", observationModel.windDirection, observationModel.windSpeed)); } } else { tooltips.push(observationModel.windSpeed); } } if (plasmoid.nativeInterface.pressureShownInTooltip && observationModel.pressure) { if (observationModel.pressureTendency) { tooltips.push(i18nc("pressure (tendency)", "%1 (%2)", observationModel.pressure, observationModel.pressureTendency)); } else { tooltips.push(observationModel.pressure); } } if (plasmoid.nativeInterface.humidityShownInTooltip && observationModel.humidity) { tooltips.push(i18n("Humidity: %1", observationModel.humidity)); } return tooltips.join("\n"); } Plasmoid.associatedApplicationUrls: generalModel.creditUrl || null Plasmoid.compactRepresentation: CompactRepresentation { generalModel: root.generalModel observationModel: root.observationModel } Plasmoid.fullRepresentation: FullRepresentation { generalModel: root.generalModel observationModel: root.observationModel } Component.onCompleted: { // workaround for missing note about being in systray or similar (kde bug #388995) // guess from cointainer structure data and make available to config page plasmoid.nativeInterface.needsToBeSquare = (plasmoid.parent !== null && ((plasmoid.parent.pluginName === 'org.kde.plasma.private.systemtray' || plasmoid.parent.objectName === 'taskItemContainer'))); } } diff --git a/dataengines/comic/comicprovider.cpp b/dataengines/comic/comicprovider.cpp index 6b6493613..8b34f79d4 100644 --- a/dataengines/comic/comicprovider.cpp +++ b/dataengines/comic/comicprovider.cpp @@ -1,344 +1,347 @@ /* * Copyright (C) 2007 Tobias Koenig * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "comicprovider.h" #include #include #include #include #include #include class ComicProvider::Private { public: Private(const KPluginMetaData &data, ComicProvider *parent) : mParent(parent), mIsCurrent(false), mFirstStripNumber(1), mComicDescription(data) { mTimer = new QTimer(parent); mTimer->setSingleShot(true); mTimer->setInterval(15000);//timeout after 15 seconds connect(mTimer, SIGNAL(timeout()), mParent, SLOT(slotTimeout())); } void jobDone(KJob *job) { if (job->error()) { mParent->pageError(job->property("uid").toInt(), job->errorText()); } else { KIO::StoredTransferJob *storedJob = qobject_cast(job); mParent->pageRetrieved(job->property("uid").toInt(), storedJob->data()); } } void slotRedirection(KIO::Job *job, QUrl newUrl) { slotRedirection(job, QUrl(), newUrl); } void slotRedirection(KIO::Job *job, QUrl oldUrl, QUrl newUrl) { Q_UNUSED(oldUrl) mParent->redirected(job->property("uid").toInt(), newUrl); mRedirections.remove(job); } void slotRedirectionDone(KJob *job) { if (job->error()) { qDebug() << "Redirection job with id" << job->property("uid").toInt() << "finished with an error."; } if (mRedirections.contains(job)) { //no redirection took place, return the original url mParent->redirected(job->property("uid").toInt(), mRedirections[job]); mRedirections.remove(job); } } void slotTimeout() { //operation took too long, abort it emit mParent->error(mParent); } void slotFinished() { //everything finished, stop the timeout timer mTimer->stop(); } ComicProvider *mParent; QString mRequestedId; QString mRequestedComicName; QString mComicAuthor; QUrl mImageUrl; bool mIsCurrent; bool mIsLeftToRight; bool mIsTopToBottom; QDate mRequestedDate; QDate mFirstStripDate; int mRequestedNumber; int mFirstStripNumber; KPluginMetaData mComicDescription; QTimer *mTimer; QHash< KJob*, QUrl > mRedirections; }; ComicProvider::ComicProvider(QObject *parent, const QVariantList &args) : QObject(parent), d(new Private( KPluginMetaData(args.count() > 2 ? args[2].toString() : QString()), this)) { Q_ASSERT(args.count() >= 2); const QString type = args[0].toString(); if (type == QLatin1String("Date")) d->mRequestedDate = args[1].toDate(); else if (type == QLatin1String("Number")) d->mRequestedNumber = args[1].toInt(); else if (type == QLatin1String("String")) { d->mRequestedId = args[1].toString(); int index = d->mRequestedId.indexOf(QLatin1Char(':')); d->mRequestedComicName = d->mRequestedId.mid(0, index); } else { Q_ASSERT(false && "Invalid type passed to comic provider"); } d->mTimer->start(); connect(this, SIGNAL(finished(ComicProvider*)), this, SLOT(slotFinished())); } ComicProvider::~ComicProvider() { delete d; } QString ComicProvider::nextIdentifier() const { if (identifierType() == DateIdentifier && d->mRequestedDate != QDate::currentDate()) return d->mRequestedDate.addDays(1).toString(Qt::ISODate); return QString(); } QString ComicProvider::previousIdentifier() const { if ((identifierType() == DateIdentifier) && (!firstStripDate().isValid() || d->mRequestedDate > firstStripDate())) return d->mRequestedDate.addDays(-1).toString(Qt::ISODate); return QString(); } QString ComicProvider::stripTitle() const { return QString(); } QString ComicProvider::additionalText() const { return QString(); } void ComicProvider::setIsCurrent(bool value) { d->mIsCurrent = value; } bool ComicProvider::isCurrent() const { return d->mIsCurrent; } QDate ComicProvider::requestedDate() const { return d->mRequestedDate; } QDate ComicProvider::firstStripDate() const { return d->mFirstStripDate; } QString ComicProvider::comicAuthor() const { return d->mComicAuthor; } void ComicProvider::setComicAuthor(const QString &author) { d->mComicAuthor = author; } void ComicProvider::setFirstStripDate(const QDate &date) { d->mFirstStripDate = date; } int ComicProvider::firstStripNumber() const { return d->mFirstStripNumber; } void ComicProvider::setFirstStripNumber(int number) { d->mFirstStripNumber = number; } QString ComicProvider::firstStripIdentifier() const { if ((identifierType() == DateIdentifier) && d->mFirstStripDate.isValid()) { return d->mFirstStripDate.toString(Qt::ISODate); } else if (identifierType() == NumberIdentifier) { return QString::number(d->mFirstStripNumber); } return QString(); } int ComicProvider::requestedNumber() const { return d->mRequestedNumber; } QString ComicProvider::requestedString() const { return d->mRequestedId; } QString ComicProvider::requestedComicName() const { return d->mRequestedComicName; } void ComicProvider::requestPage(const QUrl &url, int id, const MetaInfos &infos) { //each request restarts the timer d->mTimer->start(); if (id == Image) { d->mImageUrl = url; } KIO::StoredTransferJob *job; if (id == Image) { //use cached information for the image if available job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); } else { //for webpages we always reload, making sure, that changes are recognised job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); } job->setProperty("uid", id); connect(job, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*))); if (!infos.isEmpty()) { QMapIterator it(infos); while (it.hasNext()) { it.next(); job->addMetaData(it.key(), it.value()); } } } void ComicProvider::requestRedirectedUrl(const QUrl &url, int id, const MetaInfos &infos) { + //each request restarts the timer + d->mTimer->start(); + KIO::MimetypeJob *job = KIO::mimetype(url, KIO::HideProgressInfo); job->setProperty("uid", id); d->mRedirections[job] = url; connect(job, SIGNAL(redirection(KIO::Job*,QUrl)), this, SLOT(slotRedirection(KIO::Job*,QUrl))); connect(job, SIGNAL(permanentRedirection(KIO::Job*,QUrl,QUrl)), this, SLOT(slotRedirection(KIO::Job*,QUrl,QUrl))); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRedirectionDone(KJob*))); if (!infos.isEmpty()) { QMapIterator it(infos); while (it.hasNext()) { it.next(); job->addMetaData(it.key(), it.value()); } } } void ComicProvider::pageRetrieved(int, const QByteArray&) { } void ComicProvider::pageError(int, const QString&) { } void ComicProvider::redirected(int, const QUrl&) { } QString ComicProvider::pluginName() const { if (!d->mComicDescription.isValid()) { return QString(); } return d->mComicDescription.pluginId(); } QString ComicProvider::name() const { if (!d->mComicDescription.isValid()) { return QString(); } return d->mComicDescription.name(); } QString ComicProvider::suffixType() const { if (!d->mComicDescription.isValid()) { return QString(); } return d->mComicDescription.value(QLatin1String("X-KDE-PlasmaComicProvider-SuffixType")); } KPluginMetaData ComicProvider::description() const { return d->mComicDescription; } QUrl ComicProvider::shopUrl() const { return QUrl(); } QUrl ComicProvider::imageUrl() const { return d->mImageUrl; } bool ComicProvider::isLeftToRight() const { return true; } bool ComicProvider::isTopToBottom() const { return true; } #include "moc_comicprovider.cpp"