diff --git a/wallpapers/CMakeLists.txt b/wallpapers/CMakeLists.txt --- a/wallpapers/CMakeLists.txt +++ b/wallpapers/CMakeLists.txt @@ -4,3 +4,5 @@ # plasma_install_package(animals org.kde.animals wallpapers wallpaper) plasma_install_package(haenau org.kde.haenau wallpapers wallpaper) plasma_install_package(hunyango org.kde.hunyango wallpapers wallpaper) +plasma_install_package(iotd org.kde.iotd wallpapers wallpaper) + diff --git a/wallpapers/iotd/CREDITS b/wallpapers/iotd/CREDITS new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/CREDITS @@ -0,0 +1,2 @@ +The default wallpaper was created by Russell Greene +during Google Code-In 2015 diff --git a/wallpapers/iotd/README b/wallpapers/iotd/README new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/README @@ -0,0 +1,8 @@ +This is a plugin for Plasma 5 which displays an "Image of the Day" from +Internet sources as the wallpaper. + +Currently, sources include: + +* Bing +* NASA Astronomy Picture of the Day +* Unsplash.com diff --git a/wallpapers/iotd/contents/assets/bing.svg b/wallpapers/iotd/contents/assets/bing.svg new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/assets/bing.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/wallpapers/iotd/contents/assets/default.svg b/wallpapers/iotd/contents/assets/default.svg new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/assets/default.svg @@ -0,0 +1,92 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/wallpapers/iotd/contents/assets/nasa.svg b/wallpapers/iotd/contents/assets/nasa.svg new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/assets/nasa.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wallpapers/iotd/contents/assets/unsplash.svg b/wallpapers/iotd/contents/assets/unsplash.svg new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/assets/unsplash.svg @@ -0,0 +1,11 @@ + + + Unsplash + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + \ No newline at end of file diff --git a/wallpapers/iotd/contents/config/main.xml b/wallpapers/iotd/contents/config/main.xml new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/config/main.xml @@ -0,0 +1,43 @@ + + + + + + + + + #000000 + + + + 0 + + + + 3600000 + + + + ../assets/default.svg + + + + + + + true + + + + true + + + + true + + + + diff --git a/wallpapers/iotd/contents/ui/config.qml b/wallpapers/iotd/contents/ui/config.qml new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/ui/config.qml @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2016 Boudhayan Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser 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. + */ + +import QtQuick 2.6 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 1.4 + +import org.kde.plasma.core 2.0 +import org.kde.kquickcontrols 2.0 + +ColumnLayout { + id: root + + property var cfg_FillMode + property int cfg_ChangeInterval + property string cfg_FallbackImage + + property alias cfg_Color: bgColorButton.color + property alias cfg_SourceBing: srcBing.checked + property alias cfg_SourceNasa: srcNasa.checked + property alias cfg_SourceUnsplash: srcUnsplash.checked + + spacing: units.largeSpacing / 2 + + Row { + spacing: units.largeSpacing / 2 + + Label { + text: i18n("General") + + font.pointSize: theme.defaultFont.pointSize * 1.25 + font.bold: true + } + } + + Row { + id: sizePositionRow + spacing: units.largeSpacing / 2 + + Label { + id: sizePositionLabel + text: i18n("Size and Position:") + + width: formAlignment - units.largeSpacing + anchors.verticalCenter: sizePositionCombo.verticalCenter + horizontalAlignment: Text.AlignRight + } + + ComboBox { + id: sizePositionCombo + width: theme.mSize(theme.defaultFont).width * 24 + + model: [ + { "label": i18n("Scaled and Cropped"), "fillMode": Image.PreserveAspectCrop }, + { "label": i18n("Scaled"), "fillMode": Image.Stretch }, + { "label": i18n("Scaled, Keep Proportions"), "fillMode": Image.PreserveAspectFit }, + { "label": i18n("Centered"), "fillMode": Image.Pad }, + { "label": i18n("Tiled"), "fillMode": Image.Tile } + ] + textRole: "label" + + Component.onCompleted: { readSizePosition(); } + onCurrentIndexChanged: { writeSizePosition(); } + + function readSizePosition() { + for (var i in model) { + if (model[i]["fillMode"] == wallpaper.configuration.FillMode) { + sizePositionCombo.currentIndex = i; + return; + } + } + } + + function writeSizePosition() { + cfg_FillMode = model[sizePositionCombo.currentIndex]["fillMode"]; + } + } + } + + Row { + id: bgColorRow + spacing: units.largeSpacing / 2 + + Label { + id: bgColorLabel + text: i18n("Background Color:") + + width: formAlignment - units.largeSpacing + anchors.verticalCenter: bgColorButton.verticalCenter + horizontalAlignment: Text.AlignRight + } + + ColorButton { + id: bgColorButton + width: units.gridUnit * 3 + + showAlphaChannel: false + dialogTitle: i18n("Background Color") + } + } + + Row { + id: intervalRow + spacing: units.largeSpacing / 2 + + Label { + id: intervalLabel + text: i18n("Change Interval:") + + width: formAlignment - units.largeSpacing + anchors.verticalCenter: intervalSettingsRow.verticalCenter + horizontalAlignment: Text.AlignRight + } + + Row { + id: intervalSettingsRow + spacing: units.smallSpacing / 2 + + SpinBox { + id: intervalHours + minimumValue: 0 + maximumValue: 23 + + Component.onCompleted: { + value = Math.floor(wallpaper.configuration.ChangeInterval / 3600000); + } + onValueChanged: { intervalSettingsRow.validateChange(); } + } + + Label { + text: i18n("hours") + anchors.verticalCenter: intervalHours.verticalCenter + } + + SpinBox { + id: intervalMinutes + minimumValue: 0 + maximumValue: 59 + + Component.onCompleted: { + value = Math.floor((wallpaper.configuration.ChangeInterval % 3600000) / 60000) + } + onValueChanged: { intervalSettingsRow.validateChange(); } + } + + Label { + text: i18n("minutes") + anchors.verticalCenter: intervalMinutes.verticalCenter + } + + function validateChange() { + if (intervalHours.value < 1) { + intervalMinutes.minimumValue = 15 + } else { + intervalMinutes.minimumValue = 0 + } + + var hours = intervalHours.value * 3600000 + var minutes = intervalMinutes.value * 60000 + cfg_ChangeInterval = hours + minutes + } + } + } + + Row { + id: fallbackImageRow + spacing: units.largeSpacing / 2 + + Label { + id: fallbackImageLabel + text: i18n("Fallback Image:") + + width: formAlignment - units.largeSpacing + anchors.verticalCenter: fallbackImageButton.verticalCenter + horizontalAlignment: Text.AlignRight + } + + Button { + id: fallbackImageButton + width: theme.mSize(theme.defaultFont).width * 24 + text: { + var url = cfg_FallbackImage + return url.substring(url.lastIndexOf("/") + 1) + } + onClicked: { fallbackImageDialog.open(); } + + FileDialog { + id: fallbackImageDialog + + title: i18n("Please choose an image") + modality: Qt.WindowModal + folder: shortcuts.pictures + + nameFilters: [ "Image files (*.jpg *.jpe *.jpeg *.png *.bmp *.svg *.svgz)" ] + selectExisting: true + selectFolder: false + selectMultiple: false + + onAccepted: { cfg_FallbackImage = fileUrl; } + } + } + } + + Row { + spacing: units.largeSpacing / 2 + + Label { + text: i18n("Sources") + + font.pointSize: theme.defaultFont.pointSize * 1.25 + font.bold: true + } + } + + Column { + spacing: units.largeSpacing / 2 + leftPadding: units.gridUnit + + CheckBox { + id: srcBing + text: i18n("Bing") + } + + CheckBox { + id: srcNasa + text: i18n("NASA Astronomy Picture of the Day") + } + + CheckBox { + id: srcUnsplash + text: i18n("Featured Images from Unsplash.com") + } + } + + Item { + Layout.fillHeight: true + } +} diff --git a/wallpapers/iotd/contents/ui/main.qml b/wallpapers/iotd/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/ui/main.qml @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Boudhayan Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser 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. + */ + +import QtQuick 2.5 +import org.kde.plasma.core 2.0 + +import "./sources.js" as Sources + +Rectangle { + id: iotdRoot; + objectName: "iotdRoot"; + + anchors.fill: parent; + color: wallpaper.configuration.Color; + + readonly property var allSources: [ + "SourceBing", + "SourceNasa", + "SourceUnsplash" + ]; + property var enabledSources: []; + + function evalEnabledSources() { + enabledSources.length = 0; + for (var i in allSources) { + if (wallpaper.configuration[allSources[i]] == true) { + enabledSources.push(allSources[i]); + } + } + } + + function changeImage() { + evalEnabledSources(); + if (enabledSources.length == 0) { + setFallbackCallback(); + return; + } + + var index = Math.floor(Math.random() * enabledSources.length); + var source = enabledSources[index]; + var imgfunc = Sources.ImageSources[source]; + + imgfunc(setImageCallback, setFallbackCallback); + } + + function setImageCallback(imageUrl, imageTitleCopyright, sourceLogo) { + iotdImage.source = imageUrl; + iotdSourceLogo.source = sourceLogo; + iotdCopyrightLabel.text = imageTitleCopyright; + + iotdSlideshowTimer.interval = wallpaper.configuration.ChangeInterval; + iotdSlideshowTimer.start(); + } + + function setFallbackCallback() { + iotdImage.source = wallpaper.configuration.FallbackImage; + iotdSourceLogo.source = ""; + iotdCopyrightLabel.text = ""; + + iotdSlideshowTimer.interval = 60000; + iotdSlideshowTimer.start(); + } + + Image { + id: iotdImage; + objectName: "iotdImage"; + anchors.fill: parent; + + source: wallpaper.configuration.FallbackImage; + fillMode: wallpaper.configuration.FillMode; + cache: true; + asynchronous: true; + mipmap: true; + + Component.onCompleted: { + changeImage(); + } + + Image { + id: iotdSourceLogo; + objectName: "iotdSourceLogo"; + + width: (parent.width / 100) * 7.5; + height: (parent.height / 100) * 7.5; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + anchors.bottomMargin: Math.min((parent.height / 100) * 7.5, (parent.width / 100) * 7.5); + anchors.rightMargin: units.smallSpacing; + opacity: 0.25; + + fillMode: Image.PreserveAspectFit; + cache: false; + asynchronous: true; + mipmap: true; + } + + Text { + id: iotdCopyrightLabel; + objectName: "iotdCopyrightLabel"; + + anchors.top: iotdSourceLogo.bottom; + anchors.right: iotdSourceLogo.right; + anchors.margins: 5; + + color: Qt.rgba(0.75, 0.75, 0.75, 0.95); + font.weight: Font.DemiBold; + font.pointSize: 9; + } + } + + Timer { + id: iotdSlideshowTimer; + objectName: "iotdSlideshowTimer"; + + running: false; + interval: wallpaper.configuration.ChangeInterval; + repeat: false; + + onTriggered: { + evalEnabledSources(); + changeImage(); + } + } +} diff --git a/wallpapers/iotd/contents/ui/sources.js b/wallpapers/iotd/contents/ui/sources.js new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/contents/ui/sources.js @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Boudhayan Gupta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser 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. + */ + +var ImageSources = { + SourceBing: bingIotd, + SourceNasa: nasaApod, + SourceUnsplash: unsplashApi +} + +function bingIotd(callback, error) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) { + var data = JSON.parse(xhr.responseText).images[0]; + var baseUrl = "https://www.bing.com"; + var imgSrc = baseUrl + data.url; + callback(imgSrc, data.copyright, "../assets/bing.svg"); + } else { + error(); + } + } + xhr.open("GET", "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-NZ", true); + xhr.send(); +} + +function nasaApod(callback, error) { + var apiKey = "s2fu4xGkpRbhU2CPwNPDDZfWDtCF8eM22L8UUY6Q"; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) { + var data = JSON.parse(xhr.responseText); + var text = data.title; + if (typeof(data.copyright) == "string") { + text = text + ", by " + data.copyright; + } + callback(data.hdurl, text, "../assets/nasa.svg"); + } else { + error(); + } + } + xhr.open("GET", "https://api.nasa.gov/planetary/apod?hd=true&api_key=" + apiKey, true); + xhr.send(); +} + +function unsplashApi(callback, error) { + var appKey = "c0dfd678a1eb11d1ed039914f9aa9507a7e8759e133a27fa15ae8be9622a879f"; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) { + var data = JSON.parse(xhr.responseText); + var copy = "By " + data.user.name + ", " + data.created_at.slice(0, 4); + callback(data.urls.raw, copy, "../assets/unsplash.svg"); + } else { + error(); + } + } + xhr.open("GET", "https://api.unsplash.com/photos/random?w=3840&h=2160", true); + xhr.setRequestHeader("Accept-Version", "v1"); + xhr.setRequestHeader("Authorization", "Client-ID " + appKey); + xhr.send(); +} diff --git a/wallpapers/iotd/metadata.desktop b/wallpapers/iotd/metadata.desktop new file mode 100644 --- /dev/null +++ b/wallpapers/iotd/metadata.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Encoding=UTF-8 +Keywords= +Icon=preferences-desktop-wallpaper +Name=Image of the Day +Type=Service + +X-KDE-ServiceTypes=Plasma/Wallpaper +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Boudhayan Gupta +X-KDE-PluginInfo-Category=Wallpaper +X-KDE-PluginInfo-Email=bgupta@kde.org +X-KDE-PluginInfo-License=LGPLv2+ +X-KDE-PluginInfo-Name=org.kde.iotd +X-KDE-PluginInfo-Version=1.0.0 +X-KDE-PluginInfo-Website=https://www.kde.org/ +X-KDE-PluginInfo-EnabledByDefault=true +X-Plasma-MainScript=ui/main.qml