import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Shapes 1.1 import QtGraphicalEffects 1.12 import NERvGear 1.0 as NVG import NERvGear.Controls 1.0 import NERvGear.Templates 1.0 as T import NERvGear.Preferences 1.0 as P import "utils.js" as Utils T.Widget { id: widget title: qsTr("Weather Widget") solid: true readonly property var fonts: Qt.fontFamilies() readonly property var fontweight: [Font.Light, Font.Normal, Font.Bold] readonly property var sfontweight: [qsTr("Light"), qsTr("Normal"), qsTr("Bold")] readonly property var configs: widget.settings.styles ?? {"Location":"","Display Location":"","Update Interval":{"Value":1,"Unit":1},"Background Color":"#ffa502","Background Opacity":60,"Area Opacity Difference":17,"Icon Color":"#fefefe","Temperature Text Settings":{"Font Color":"#f5f5f5","Font Size":90,"Font Name":fonts.length-1,"Font Weight":1,"X Offset":33,"Y Offset":32},"Area Text Settings":{"Font Color":"#f5f5f5","Font Size":60,"Font Name":fonts.length-1,"Font Weight":1,"X Offset":0,"Y Offset":-56,"Border Margin":40}} readonly property real w: widget.width readonly property real h: 0.46*widget.width Item { id: main width: w height: h anchors.centerIn: parent layer.enabled: true layer.effect: OpacityMask{ maskSource: Rectangle { width: w height: h color: "black" radius: h/15 } } Rectangle { id: weather_box width: w height: h anchors.left: parent.left anchors.top: parent.top opacity: configs["Background Opacity"]/100 color: configs["Background Color"] } Item { id: sdialog width: h/8 height: h/8 anchors.left: parent.left anchors.leftMargin: h/16 anchors.topMargin: h/14 anchors.top: parent.top Shape { anchors.fill: parent ShapePath { strokeWidth: parent.height/120 strokeColor: configs["Icon Color"] startX: h/44; startY: h/800 PathLine { x: h*0.10227272727272727; y: h/800 } } ShapePath { strokeWidth: parent.height/120 strokeColor: configs["Icon Color"] startX: h/44; startY: h/800 + h/32 PathLine { x: h*0.10227272727272727; y: h/800 + h/32 } } ShapePath { strokeWidth: parent.height/120 strokeColor: configs["Icon Color"] startX: h/44; startY: h/800 + h/16 PathLine { x: h*0.10227272727272727; y: h/800 + h/16 } } } MouseArea { anchors.fill: parent enabled: !styleDialog.active onClicked: { styleDialog.active = true; } } } Item { id: weather width: 0.655*w height: h anchors.left: parent.left anchors.top: parent.top Image { id: weather_mask anchors.centerIn: weather autoTransform: true visible: false source: "../Images/Weather/Unknown.png" } Rectangle { id: wcolor width: h/1.1 height: h/1.1 anchors.centerIn: weather visible: false color: configs["Icon Color"] } OpacityMask { anchors.fill: wcolor source: wcolor maskSource: weather_mask } } Rectangle { id: temper_box width: 0.345*w height: h anchors.right: parent.right anchors.top: parent.top opacity: configs["Background Opacity"]*configs["Area Opacity Difference"]/10000 color: "black" } Text { id: area anchors.centerIn: temper_box anchors.horizontalCenterOffset: temper_box.width*configs["Area Text Settings"]["X Offset"]/200 anchors.verticalCenterOffset: h*configs["Area Text Settings"]["Y Offset"]/200 color: configs["Area Text Settings"]["Font Color"] text: "" font.pixelSize: w*0.0009*configs["Area Text Settings"]["Font Size"] font.family: fonts[configs["Area Text Settings"]["Font Name"]] font.weight: fontweight[configs["Area Text Settings"]["Font Weight"]] Rectangle { anchors.fill: parent anchors.margins: -area.font.pixelSize*configs["Area Text Settings"]["Border Margin"]/100 color: "transparent" border.color: configs["Area Text Settings"]["Font Color"] border.width: area.font.pixelSize/15 radius: area.font.pixelSize/3.5 visible: Boolean(area.text) } } Text { id: temperature anchors.centerIn: temper_box anchors.horizontalCenterOffset: temper_box.width*configs["Temperature Text Settings"]["X Offset"]/200 anchors.verticalCenterOffset: h*configs["Temperature Text Settings"]["Y Offset"]/200 color: configs["Temperature Text Settings"]["Font Color"] text: "--°" font.pixelSize: w*0.002*configs["Temperature Text Settings"]["Font Size"] font.family: fonts[configs["Temperature Text Settings"]["Font Name"]] font.weight: fontweight[configs["Temperature Text Settings"]["Font Weight"]] } } function setWeather() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState === XMLHttpRequest.DONE) { let data = xhr.responseText.toString(); if (data) { let jdata = JSON.parse(data); temperature.text = jdata["current_condition"][0]["temp_C"] + "°"; if (!configs["Display Location"] && !configs["Location"]) { area.text = jdata["nearest_area"][0]["areaName"][0]["value"]; } weather_mask.source = "../Images/Weather/" + Utils.weather_codes[jdata["current_condition"][0]["weatherCode"]] ?? "Unknown" + ".png"; } else { weather_mask.source = "../Images/Weather/Unknown.png"; } } } if (configs["Location"]) xhr.open("GET", 'https://wttr.in/'+configs["Location"]+'?format=j1'); else xhr.open("GET", 'https://wttr.in/?format=j1'); xhr.send(); if (configs["Display Location"]) { area.text = configs["Display Location"]; } else if (configs["Location"]) { area.text = configs["Location"] } } Timer { id: timer interval: 600000 running: widget.NVG.View.exposed repeat: true triggeredOnStart: true onTriggered: { setWeather(); } } menu: Menu { Action { text: qsTr("Settings") + "..." onTriggered: styleDialog.active = true } Action { text: qsTr("Refresh") onTriggered: timer.restart() } } Loader { id: styleDialog active: false sourceComponent: NVG.Window { id: window title: qsTr("Settings") visible: true minimumWidth: 450 minimumHeight: 550 width: minimumWidth height: minimumHeight transientParent: widget.NVG.View.window property var configuration Page { id: cfg_page anchors.fill: parent header: TitleBar { text: qsTr("Settings") standardButtons: Dialog.Save | Dialog.Reset onAccepted: { configuration = rootPreference.save(); widget.settings.styles = configuration; styleDialog.active = false; timer.interval = 60000*configs["Update Interval"]["Value"]*(1+59*configs["Update Interval"]["Unit"]) setWeather(); } onReset: { rootPreference.load(); let cfg = rootPreference.save(); widget.settings.styles = cfg; } } ColumnLayout { id: root anchors.fill: parent anchors.margins: 16 anchors.topMargin: 0 Flickable { Layout.fillWidth: true Layout.fillHeight: true clip: true contentWidth: preferenceLayout.implicitWidth contentHeight: preferenceLayout.implicitHeight ColumnLayout { id: preferenceLayout width: root.width P.PreferenceGroup { id: rootPreference Layout.fillWidth: true label: qsTr("Configuration") onPreferenceEdited: { widget.settings.styles = rootPreference.save(); } P.TextFieldPreference { name: "Location" label: qsTr("Location") message: qsTr("Search address by location, latitude and longitude.") } P.TextFieldPreference { name: "Display Location" label: qsTr("Display Location") message: "The location to display in widget." } P.DialogPreference { name: "Update Interval" label: qsTr("Update Interval") live: true displayValue: _cfg_update_interval_value.value + " " + [qsTr("Minutes"), qsTr("Hours")][_cfg_update_interval_unit.value] P.SpinPreference { id: _cfg_update_interval_value name: "Value" from: 1 to: 1440 editable: true defaultValue: 1 } P.SelectPreference { id: _cfg_update_interval_unit name: "Unit" label: qsTr("Unit") defaultValue: 1 model: [qsTr("Minutes"), qsTr("Hours")] } } P.ColorPreference { name: "Background Color" label: qsTr("Background Color") defaultValue: "#ffa502" } P.SliderPreference { name: "Background Opacity" label: qsTr("Background Opacity") from: 0 to: 100 stepSize: 1 defaultValue: 60 displayValue: value + "%" } P.SliderPreference { name: "Area Opacity Difference" label: qsTr("Area Opacity Difference") from: 0 to: 100 stepSize: 1 defaultValue: 17 displayValue: value + "%" } P.ColorPreference { name: "Icon Color" label: qsTr("Icon Color") defaultValue: "#fefefe" } P.DialogPreference { name: "Temperature Text Settings" label: qsTr("Temperature Text Settings") icon.name: "solid:\uf1fc" live: true P.ColorPreference { name: "Font Color" label: qsTr("Font Color") defaultValue: "#f5f5f5" } P.SliderPreference { name: "Font Size" label: qsTr("Font Size") from: 1 to: 100 stepSize: 1 defaultValue: 90 displayValue: value + "%" } P.SelectPreference { name: "Font Name" label: qsTr("Font Style") defaultValue: fonts.length-1 model: fonts } P.SelectPreference { name: "Font Weight" label: qsTr("Font Weight") defaultValue: 1 model: sfontweight } P.SliderPreference { name: "X Offset" label: qsTr("X Offset") from: -100 to: 100 stepSize: 1 defaultValue: 33 displayValue: value + "%" } P.SliderPreference { name: "Y Offset" label: qsTr("Y Offset") from: -100 to: 100 stepSize: 1 defaultValue: 32 displayValue: value + "%" } } P.DialogPreference { name: "Area Text Settings" label: qsTr("Area Text Settings") icon.name: "solid:\uf1fc" live: true P.ColorPreference { name: "Font Color" label: qsTr("Font Color") defaultValue: "#f5f5f5" } P.SliderPreference { name: "Font Size" label: qsTr("Font Size") from: 1 to: 100 stepSize: 1 defaultValue: 60 displayValue: value + "%" } P.SelectPreference { name: "Font Name" label: qsTr("Font Style") defaultValue: fonts.length-1 model: fonts } P.SelectPreference { name: "Font Weight" label: qsTr("Font Weight") defaultValue: 1 model: sfontweight } P.SliderPreference { name: "X Offset" label: qsTr("X Offset") from: -100 to: 100 stepSize: 1 defaultValue: 0 displayValue: value + "%" } P.SliderPreference { name: "Y Offset" label: qsTr("Y Offset") from: -100 to: 100 stepSize: 1 defaultValue: -56 displayValue: value + "%" } P.SliderPreference { name: "Border Margin" label: qsTr("Border Margin") from: 0 to: 100 stepSize: 1 defaultValue: 40 displayValue: value + "%" } } Component.onCompleted: { if(!widget.settings.styles) { configuration = rootPreference.save(); widget.settings.styles = configuration; } rootPreference.load(widget.settings.styles); configuration = widget.settings.styles; } } } } } } onClosing: { widget.settings.styles = configuration; styleDialog.active = false; } } } }