407 lines
16 KiB
QML
407 lines
16 KiB
QML
import QtQuick 2.3
|
|
import QtQuick.Controls 2.3
|
|
import QtQuick.Layouts 1.1
|
|
import Nymea 1.0
|
|
import Nymea.AirConditioning 1.0
|
|
import "qrc:/ui/components"
|
|
|
|
ColumnLayout {
|
|
id: root
|
|
Layout.minimumHeight: 100
|
|
property string title: ""
|
|
|
|
property ZoneInfo zone: null
|
|
property TemperatureDaySchedule daySchedule: null
|
|
|
|
property TemperatureDaySchedule scheduleClipboard: null
|
|
|
|
signal copyClicked()
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: root.title
|
|
}
|
|
|
|
ProgressButton {
|
|
imageSource: paste ? "edit-paste" : "edit-copy"
|
|
color: root.scheduleClipboard == root.daySchedule ? Style.accentColor : Style.iconColor
|
|
property bool paste: root.scheduleClipboard != null && root.scheduleClipboard != root.daySchedule
|
|
onClicked: {
|
|
if (paste) {
|
|
daySchedule.clear();
|
|
for (var i = 0; i < root.scheduleClipboard.count; i++) {
|
|
var schedule = root.scheduleClipboard.get(i)
|
|
daySchedule.createSchedule(schedule.startTime, schedule.endTime, schedule.temperature)
|
|
}
|
|
} else {
|
|
root.copyClicked()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
QtObject {
|
|
id: d
|
|
property var freeBlocks: {
|
|
var ret = []
|
|
for (var i = 0; i < daySchedule.count; i++) {
|
|
var previous = i == 0 ? null : daySchedule.get(i - 1)
|
|
var previousMins = previous ? previous.endTime.getHours() * 60 + previous.endTime.getMinutes() : 0
|
|
var schedule = daySchedule.get(i)
|
|
var startMins = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
|
var endMins = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
|
|
|
if (startMins > previousMins + 60 * 3) {
|
|
ret.push({startMins: previousMins, endMins: startMins})
|
|
}
|
|
}
|
|
var last = daySchedule.count > 0 ? daySchedule.get(daySchedule.count - 1) : null
|
|
var lastMins = last ? last.endTime.getHours() * 60 + last.endTime.getMinutes() : 0
|
|
if (lastMins < (21*60)) {
|
|
ret.push({startMins: lastMins, endMins: 24*60})
|
|
}
|
|
|
|
return ret
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: slider
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
color: Style.tileBackgroundColor
|
|
radius: Style.cornerRadius
|
|
clip: true
|
|
|
|
|
|
Repeater {
|
|
model: 24
|
|
delegate: Rectangle {
|
|
height: parent.height
|
|
width: 1
|
|
color: Style.tileOverlayColor
|
|
x: slider.width / 24 * index
|
|
visible: index > 0
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: root.daySchedule
|
|
delegate: Rectangle {
|
|
id: blockDelegate
|
|
readonly property TemperatureSchedule schedule: root.daySchedule.get(index)
|
|
readonly property int startMinutes: schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
|
readonly property int endMinutes: schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
|
readonly property int totalMinutes: 24 * 60
|
|
height: slider.height
|
|
width: endMinutes * slider.width / totalMinutes - x
|
|
x: startMinutes * slider.width / totalMinutes
|
|
radius: Style.cornerRadius
|
|
color: schedule.temperature >= zone.standbySetpoint ? Style.red : Style.blue
|
|
|
|
Rectangle {
|
|
anchors {
|
|
left: parent.left;
|
|
leftMargin: Style.extraSmallMargins
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
height: Style.font.pixelSize
|
|
width: 2
|
|
radius: width / 2
|
|
color: Style.white
|
|
}
|
|
Rectangle {
|
|
anchors {
|
|
right: parent.right
|
|
rightMargin: Style.extraSmallMargins
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
height: Style.font.pixelSize
|
|
width: 2
|
|
radius: width / 2
|
|
color: Style.white
|
|
}
|
|
|
|
Label {
|
|
anchors { left: parent.left; right: parent.right; margins: Style.extraSmallMargins; top: parent.top }
|
|
anchors.leftMargin: parent.width >= implicitWidth + Style.smallMargins ? Style.extraSmallMargins : -(implicitWidth + Style.extraSmallMargins)
|
|
horizontalAlignment: Text.AlignLeft
|
|
font: Style.extraSmallFont
|
|
text: blockDelegate.schedule.startTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
|
}
|
|
|
|
Label {
|
|
anchors { left: parent.left; right: parent.right; margins: Style.smallMargins; verticalCenter: parent.verticalCenter }
|
|
horizontalAlignment: Text.AlignHCenter
|
|
font: Style.smallFont
|
|
text: Types.toUiValue(blockDelegate.schedule.temperature, Types.UnitDegreeCelsius) + "°"
|
|
elide: Text.ElideRight
|
|
}
|
|
Label {
|
|
anchors { left: parent.left; right: parent.right; margins: Style.extraSmallMargins; bottom: parent.bottom }
|
|
anchors.rightMargin: parent.width >= implicitWidth + Style.smallMargins ? Style.extraSmallMargins : -(implicitWidth + Style.extraSmallMargins)
|
|
horizontalAlignment: Text.AlignRight
|
|
font: Style.extraSmallFont
|
|
text: blockDelegate.schedule.endTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
|
// elide: Text.ElideRight
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
|
|
property bool moveStart: false
|
|
property bool moveEnd: false
|
|
property TemperatureSchedule previousSchedule: null
|
|
property TemperatureSchedule movedSchedule: null
|
|
property TemperatureSchedule nextSchedule: null
|
|
|
|
property int startMouseX: 0
|
|
property int startMins: 0
|
|
property int endMins: 0
|
|
|
|
onPressed: {
|
|
movedSchedule = null
|
|
startMouseX = mouseX
|
|
var totalMins = 24 * 60
|
|
for (var i = 0; i < root.daySchedule.count; i++) {
|
|
var schedule = root.daySchedule.get(i)
|
|
print("schedule:", schedule.startTime, schedule.endTime)
|
|
var startMin = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
|
|
var startPos = startMin * slider.width / totalMins
|
|
var endMin = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
|
|
var endPos = endMin * slider.width / totalMins
|
|
|
|
if (Math.abs(startPos - mouseX) < 10) {
|
|
moveStart = true;
|
|
print("start")
|
|
} else if (Math.abs(endPos - mouseX) < 10) {
|
|
moveEnd = true
|
|
print("end")
|
|
} else if (mouseX > startPos && mouseX < endPos) {
|
|
moveStart = true
|
|
moveEnd = true
|
|
print("middle")
|
|
} else {
|
|
continue
|
|
}
|
|
startMins = startMin
|
|
endMins = endMin
|
|
previousSchedule = i > 0 ? root.daySchedule.get(i-1) : null
|
|
movedSchedule = schedule
|
|
nextSchedule = i < root.daySchedule.count - 1 ? root.daySchedule.get(i+1) : null
|
|
break;
|
|
}
|
|
}
|
|
|
|
onReleased: {
|
|
moveStart = false;
|
|
moveEnd = false;
|
|
preventStealing = false;
|
|
}
|
|
|
|
onClicked: {
|
|
print("clicked")
|
|
if (movedSchedule != null && Math.abs(mouseX - startMouseX) < 5) {
|
|
print("opening")
|
|
var dialog = editDialogComponent.createObject(root.parent, {schedule: movedSchedule})
|
|
dialog.open()
|
|
|
|
}
|
|
}
|
|
|
|
onPositionChanged: {
|
|
var totalMins = 24 * 60
|
|
var diffX = mouseX - startMouseX
|
|
|
|
// dY : height = dM : total
|
|
var diffMins = diffX * totalMins / slider.width
|
|
print("diffX", diffX, "diffMins", diffMins, startMins, startMins + diffMins)
|
|
|
|
var newStart = new Date(movedSchedule.startTime);
|
|
var newEnd = new Date(movedSchedule.endTime);
|
|
|
|
var newStartMins = startMins + (moveStart ? diffMins : 0);
|
|
var newEndMins = endMins + (moveEnd ? diffMins : 0);
|
|
|
|
var snapMinutes = 30
|
|
|
|
var leftLimit = previousSchedule ? previousSchedule.endTime.getHours() * 60 + previousSchedule.endTime.getMinutes() + snapMinutes : 0
|
|
var rightLimit = nextSchedule ? nextSchedule.startTime.getHours() * 60 + nextSchedule.startTime.getMinutes() - snapMinutes : totalMins
|
|
|
|
|
|
if (moveStart && !moveEnd) {
|
|
newStartMins = Math.max(leftLimit, newStartMins)
|
|
newStartMins = Math.min(newEndMins - 60, newStartMins)
|
|
} else if (moveEnd && !moveStart) {
|
|
newEndMins = Math.min(rightLimit, newEndMins)
|
|
newEndMins = Math.max(newStartMins + 60, newEndMins)
|
|
} else if (moveStart && moveEnd) {
|
|
newStartMins = Math.max(leftLimit, newStartMins)
|
|
newEndMins = Math.min(rightLimit, newEndMins)
|
|
|
|
var blockSize = endMins - startMins
|
|
|
|
newStartMins = Math.max(leftLimit, Math.min(newEndMins - blockSize, newStartMins))
|
|
newEndMins = Math.max(newStartMins + blockSize, newEndMins)
|
|
|
|
}
|
|
|
|
var startSnapOffset = newStartMins % snapMinutes
|
|
if (startSnapOffset < snapMinutes / 2) {
|
|
newStartMins -= startSnapOffset
|
|
} else {
|
|
newStartMins += (snapMinutes - startSnapOffset)
|
|
}
|
|
var endSnapOffset = newEndMins % snapMinutes
|
|
if (endSnapOffset < snapMinutes / 2) {
|
|
newEndMins -= endSnapOffset
|
|
} else {
|
|
newEndMins += (snapMinutes - endSnapOffset)
|
|
}
|
|
print("startSnapOffset", startSnapOffset, "endSnapOff", endSnapOffset, "nes start", newStartMins, newEndMins)
|
|
|
|
if (newEndMins == totalMins) {
|
|
newEndMins -= 1
|
|
}
|
|
|
|
newStart.setHours(0, newStartMins)
|
|
newEnd.setHours(0, newEndMins)
|
|
|
|
if (movedSchedule.startTime.getTime() !== newStart.getTime() || movedSchedule.endTime.getTime() !== newEnd.getTime()) {
|
|
preventStealing = true;
|
|
}
|
|
|
|
movedSchedule.startTime = newStart
|
|
movedSchedule.endTime = newEnd
|
|
|
|
print("start time is new", newStart.toLocaleTimeString())
|
|
print("end time is new", newEnd.toLocaleTimeString())
|
|
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: d.freeBlocks
|
|
delegate: Item {
|
|
id: freeBlockDelegate
|
|
property var block: d.freeBlocks[index]
|
|
x: block.startMins * slider.width / (24*60)
|
|
width: block.endMins * slider.width / (24*60) - x
|
|
height: slider.height
|
|
|
|
ProgressButton {
|
|
anchors.centerIn: parent
|
|
imageSource: "add"
|
|
onClicked: {
|
|
var startTime = new Date()
|
|
var endTime = new Date()
|
|
if (root.daySchedule.count == 0) {
|
|
startTime.setHours(6, 0, 0)
|
|
endTime.setHours(18, 0, 0)
|
|
} else {
|
|
startTime.setHours(0, freeBlockDelegate.block.startMins + 60, 0)
|
|
endTime.setHours(0, freeBlockDelegate.block.endMins - 60, 0)
|
|
}
|
|
|
|
root.daySchedule.createSchedule(startTime, endTime, 21)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: editDialogComponent
|
|
NymeaDialog {
|
|
id: editDialog
|
|
x: (parent.width - width) / 2
|
|
property TemperatureSchedule schedule: null
|
|
|
|
standardButtons: Dialog.NoButton
|
|
|
|
Dial {
|
|
id: dial
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: width
|
|
activeValue: root.zone.standbySetpoint
|
|
minValue: 10
|
|
maxValue: 30
|
|
precision: 0.5
|
|
value: editDialog.schedule.temperature
|
|
onMoved: editDialog.schedule.temperature = value
|
|
color: activeValue <= value ? Style.red : Style.blue
|
|
ColumnLayout {
|
|
anchors.centerIn: parent
|
|
anchors.verticalCenterOffset: -Style.smallMargins
|
|
width: parent.contentItem.width * 0.6
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: Types.toUiUnit(Types.UnitDegreeCelsius)
|
|
font.pixelSize: Math.min(Style.smallFont.pixelSize, dial.height / 16)
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: Types.toUiValue(editDialog.schedule.temperature, Types.UnitDegreeCelsius).toFixed(1)
|
|
font.pixelSize: Math.min(Style.hugeFont.pixelSize, dial.height / 8)
|
|
horizontalAlignment: Text.AlignHCenter
|
|
color: zone.currentSetpoint > zone.standbySetpoint
|
|
? Style.red
|
|
: zone.currentSetpoint < zone.standbySetpoint
|
|
? Style.blue
|
|
: Style.foregroundColor
|
|
}
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: Types.toUiValue(zone.standbySetpoint, Types.UnitDegreeCelsius).toFixed(1)
|
|
font.pixelSize: Math.min(Style.largeFont.pixelSize, dial.height / 12)
|
|
wrapMode: Text.WordWrap
|
|
horizontalAlignment: Text.AlignHCenter
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: editDialog.schedule.startTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
|
+ " - "
|
|
+ editDialog.schedule.endTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
|
|
RowLayout {
|
|
Button {
|
|
text: qsTr("Remove")
|
|
Layout.fillWidth: true
|
|
onClicked: {
|
|
root.daySchedule.removeSchedule(editDialog.schedule)
|
|
editDialog.close()
|
|
}
|
|
}
|
|
Button {
|
|
text: qsTr("OK")
|
|
Layout.fillWidth: true
|
|
onClicked: {
|
|
editDialog.close()
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|