diff --git a/framework/src/domain/eventcontroller.cpp b/framework/src/domain/eventcontroller.cpp index 30f06600..df9c8326 100644 --- a/framework/src/domain/eventcontroller.cpp +++ b/framework/src/domain/eventcontroller.cpp @@ -1,108 +1,141 @@ /* * Copyright (C) 2017 Michael Bohlender, * Copyright (C) 2018 Christian Mollekopf, * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "eventcontroller.h" #include #include #include #include #include #include using namespace Sink::ApplicationDomain; EventController::EventController() : Kube::Controller(), action_save{new Kube::ControllerAction{this, &EventController::save}} { updateSaveAction(); } void EventController::save() { using namespace Sink; using namespace Sink::ApplicationDomain; const auto calendar = getCalendar(); if (!calendar) { qWarning() << "No calendar selected"; + return; } - auto calcoreEvent = QSharedPointer::create(); - calcoreEvent->setUid(QUuid::createUuid().toString()); - calcoreEvent->setSummary(getSummary()); - calcoreEvent->setDescription(getDescription()); - calcoreEvent->setDtStart(getStart()); - calcoreEvent->setDtEnd(getEnd()); - calcoreEvent->setAllDay(getAllDay()); - - Event event(calendar->resourceInstanceIdentifier()); - event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); - event.setCalendar(*calendar); - - auto job = Store::create(event) - .then([&] (const KAsync::Error &error) { - if (error) { - SinkWarning() << "Failed to save the event: " << error; - } - emit done(); - }); - - run(job); + if (auto e = mEvent.value()) { + Event event = *e; + + //Apply the changed properties on top of what's existing + auto calcoreEvent = KCalCore::ICalFormat().readIncidence(event.getIcal()).dynamicCast(); + if(!calcoreEvent) { + SinkWarning() << "Invalid ICal to process, ignoring..."; + return; + } + + calcoreEvent->setUid(QUuid::createUuid().toString()); + calcoreEvent->setSummary(getSummary()); + calcoreEvent->setDescription(getDescription()); + calcoreEvent->setDtStart(getStart()); + calcoreEvent->setDtEnd(getEnd()); + calcoreEvent->setAllDay(getAllDay()); + + event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); + event.setCalendar(*calendar); + + auto job = Store::modify(event) + .then([&] (const KAsync::Error &error) { + if (error) { + SinkWarning() << "Failed to save the event: " << error; + } + emit done(); + }); + + run(job); + } else { + Event event(calendar->resourceInstanceIdentifier()); + + auto calcoreEvent = QSharedPointer::create(); + calcoreEvent->setUid(QUuid::createUuid().toString()); + calcoreEvent->setSummary(getSummary()); + calcoreEvent->setDescription(getDescription()); + calcoreEvent->setDtStart(getStart()); + calcoreEvent->setDtEnd(getEnd()); + calcoreEvent->setAllDay(getAllDay()); + + event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); + event.setCalendar(*calendar); + + auto job = Store::create(event) + .then([&] (const KAsync::Error &error) { + if (error) { + SinkWarning() << "Failed to save the event: " << error; + } + emit done(); + }); + + run(job); + } } void EventController::updateSaveAction() { saveAction()->setEnabled(!getSummary().isEmpty()); } void EventController::loadEvent(const QVariant &variant) { using namespace Sink; mEvent = variant; if (auto event = variant.value()) { setCalendar(ApplicationDomainType::Ptr::create(ApplicationDomainType::createEntity(event->resourceInstanceIdentifier(), event->getCalendar()))); auto icalEvent = KCalCore::ICalFormat().readIncidence(event->getIcal()).dynamicCast(); if(!icalEvent) { SinkWarning() << "Invalid ICal to process, ignoring..."; return; } setSummary(icalEvent->summary()); setDescription(icalEvent->description()); setStart(icalEvent->dtStart()); setEnd(icalEvent->dtEnd()); setAllDay(icalEvent->allDay()); } } void EventController::remove() { if (auto c = mEvent.value()) { run(Sink::Store::remove(*c)); } } QVariant EventController::getEvent() const { return mEvent; } diff --git a/views/calendar/qml/DateTimeChooser.qml b/views/calendar/qml/DateTimeChooser.qml new file mode 100644 index 00000000..2eaf3db6 --- /dev/null +++ b/views/calendar/qml/DateTimeChooser.qml @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2017 Michael Bohlender, + * Copyright (C) 2018 Christian Mollekopf, + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.2 +import org.kube.framework 1.0 as Kube +import Qt.labs.calendar 1.0 + +import "dateutils.js" as DateUtils + +RowLayout { + id: root + property bool enableTime: true + property var notBefore: new Date(1980, 1, 1, 0, 0, 0) + property var notBeforeRounded: DateUtils.roundToMinutes(notBefore) + property var initialValue: null + property int delta: 15 + + property date dateTime: initialValue ? initialValue : new Date() + + spacing: Kube.Units.smallSpacing + + Component.onCompleted: { + if (root.initialValue) { + root.dateTime = root.initialValue + } + } + + Kube.Button { + id: button + + Layout.preferredWidth: implicitWidth + + text: selector.selectedDate.toLocaleDateString() + + onClicked: { + popup.open() + } + + Kube.Popup { + id: popup + + x: button.x + y: button.y + button.height + width: selector.implicitWidth + Kube.Units.largeSpacing * 2 + height: selector.implicitHeight + Kube.Units.largeSpacing * 2 + modal: true + focus: true + + DateSelector { + id: selector + anchors.fill: parent + //TODO add earliest date, prevent selection before + selectedDate: root.dateTime + onSelected: root.dateTime = date + + backgroundColor: Kube.Colors.backgroundColor + textColor: Kube.Colors.textColor + invertIcons: false + onNext: selectedDate = DateUtils.nextMonth(selectedDate) + onPrevious: selectedDate = DateUtils.previousMonth(selectedDate) + } + } + } + + Kube.ComboBox { + visible: enableTime + + Layout.preferredWidth: Kube.Units.gridUnit * 4 + + function generateTimes(start, delta) { + var d = new Date(2000, 1, 1, start.getHours(), start.getMinutes(), start.getSeconds()) + var list = [] + while (d.getDate() == 1) { + list.push(dateToString(d)) + d = DateUtils.addMinutesToDate(d, delta) + } + return list + } + + property var availableTimes: null + + function dateToString(date) { + return date.toLocaleTimeString(Qt.locale(), "hh:mm") + } + + function findCurrentIndex(date, delta) { + return find(dateToString(DateUtils.roundToMinutes(date, delta))) + } + + function setTimeFromIndex(index) { + var date = Date.fromLocaleTimeString(Qt.locale(), availableTimes[index], "hh:mm") + //Intermediate variable is necessary for binding to be updated + var newDate = root.dateTime + newDate.setHours(date.getHours(), date.getMinutes()) + root.dateTime = newDate + } + + Component.onCompleted: { + availableTimes = generateTimes(DateUtils.sameDay(root.notBeforeRounded, root.dateTime) ? root.notBeforeRounded : new Date(2000, 1, 1, 0, 0, 0), root.delta) + currentIndex = findCurrentIndex(root.initialValue, root.delta) + if (currentIndex >= 0) { + setTimeFromIndex(currentIndex) + } + } + + onActivated: { + setTimeFromIndex(index) + } + + model: availableTimes + } +} diff --git a/views/calendar/qml/EventEditor.qml b/views/calendar/qml/EventEditor.qml index 1fab62b7..1f572d47 100644 --- a/views/calendar/qml/EventEditor.qml +++ b/views/calendar/qml/EventEditor.qml @@ -1,217 +1,228 @@ /* * Copyright (C) 2018 Michael Bohlender, * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.4 import QtQuick.Layouts 1.1 import org.kube.framework 1.0 as Kube Kube.Popup { - id: popup + id: root property var controller: Kube.EventController {} property bool editMode: false + property date start: new Date() + + width: contentLayout.implicitWidth + 2 * Kube.Units.largeSpacing + height: contentLayout.implicitHeight + 2 * Kube.Units.largeSpacing Item { - id: root states: [ State { name: "edit" PropertyChanges { target: deleteButton; visible: true } PropertyChanges { target: abortButton; visible: false } PropertyChanges { target: saveButton; visible: true } PropertyChanges { target: discardButton; visible: true } PropertyChanges { target: createButton; visible: false } + PropertyChanges { target: calendarSelector; visible: false } }, State { name: "new" PropertyChanges { target: deleteButton; visible: false } PropertyChanges { target: abortButton; visible: true } PropertyChanges { target: saveButton; visible: false } PropertyChanges { target: discardButton; visible: false } PropertyChanges { target: createButton; visible: true } + PropertyChanges { target: calendarSelector; visible: true } } ] state: editMode ? "edit" : "new" anchors.fill: parent Item { id: eventEditor anchors.fill: parent ColumnLayout { anchors { top: parent.top left: parent.left right: parent.right bottom: buttons.top bottomMargin: Kube.Units.largeSpacing } spacing: Kube.Units.largeSpacing Kube.TextField { id: titleEdit Layout.fillWidth: true - placeholderText: "Event Title" + placeholderText: qsTr("Event Title") text: controller.summary + onTextChanged: controller.summary = text } ColumnLayout { id: dateAndTimeChooser - states: [ - State { - name: "regular" - PropertyChanges {target: fromTime; visible: true} - PropertyChanges {target: toTime; visible: true} - }, - State { - name: "daylong" - PropertyChanges {target: fromTime; visible: false} - PropertyChanges {target: toTime; visible: false} - } - ] - state: controller.allDay ? "daylong" : "regular" - spacing: Kube.Units.smallSpacing RowLayout { Layout.fillWidth: true spacing: Kube.Units.largeSpacing - RowLayout { - spacing: Kube.Units.smallSpacing - - DayChooser {} - TimeChooser { - id: fromTime - } + DateTimeChooser { + id: startDate + objectName: "startDate" + enableTime: !controller.allDay + initialValue: root.editMode ? controller.start : root.start + onDateTimeChanged: controller.start = dateTime } Kube.Label { text: qsTr("until") } - RowLayout { - spacing: Kube.Units.smallSpacing - - DayChooser {} - TimeChooser { - id: toTime - } + DateTimeChooser { + id: endDate + objectName: "endDate" + enableTime: !controller.allDay + notBefore: startDate.dateTime + initialValue: root.editMode ? controller.end : startDate.dateTime + onDateTimeChanged: controller.end = dateTime } } RowLayout { spacing: Kube.Units.smallSpacing Kube.CheckBox { onClicked: { checked: controller.allDay onClicked: { controller.allDay = !controller.allDay } } } Kube.Label { text: qsTr("All day") } } } ColumnLayout { spacing: Kube.Units.smallSpacing Layout.fillWidth: true - Kube.TextField { - Layout.fillWidth: true - placeholderText: qsTr("Location") - } + //FIXME location doesn't exist yet + // Kube.TextField { + // Layout.fillWidth: true + // placeholderText: qsTr("Location") + // text: controller.location + // onTextChanged: controller.location = text + // } Kube.TextEditor { Layout.fillWidth: true Layout.fillHeight: true //TODO placeholderText: "Description" text: controller.description + onTextChanged: controller.description = text + } + + Kube.ComboBox { + id: calendarSelector + Layout.fillWidth: true + + model: Kube.EntityModel { + id: calendarModel + type: "calendar" + roles: ["name"] + } + textRole: "name" + onActivated: { + controller.calendar = calendarModel.data(index).object + } } } } RowLayout { anchors { bottom: parent.bottom left: parent.left } Kube.Button { id: deleteButton text: qsTr("Delete") onClicked: { controller.remove() - popup.close() + root.close() } } Kube.Button { id: abortButton text: qsTr("Abort") onClicked: { - popup.close() + root.close() } } } RowLayout { id: buttons anchors { bottom: parent.bottom right: parent.right } spacing: Kube.Units.smallSpacing Kube.Button { id: discardButton text: qsTr("Discard Changes") onClicked: { - popup.close() + root.close() } } Kube.PositiveButton { id: saveButton text: qsTr("Save Changes") onClicked: { - //controller.saveAction.execute() - popup.close() + controller.saveAction.execute() + root.close() } } Kube.PositiveButton { id: createButton text: qsTr("Create Event") onClicked: { - popup.close() + controller.saveAction.execute() + root.close() } } } } } } diff --git a/views/calendar/qml/EventView.qml b/views/calendar/qml/EventView.qml index f79206dd..8f6c35bd 100644 --- a/views/calendar/qml/EventView.qml +++ b/views/calendar/qml/EventView.qml @@ -1,100 +1,100 @@ /* * Copyright (C) 2018 Michael Bohlender, * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 import org.kube.framework 1.0 as Kube FocusScope { id: root property var controller width: contentLayout.implicitWidth + 2 * Kube.Units.largeSpacing height: contentLayout.implicitHeight + 2 * Kube.Units.largeSpacing Rectangle { anchors { fill: parent } color: Kube.Colors.viewBackgroundColor ColumnLayout { id: contentLayout anchors { centerIn: parent } spacing: Kube.Units.smallSpacing Kube.Heading { width: parent.width text: controller.summary } Kube.Label { visible: controller.allDay text: controller.start.toLocaleString(Qt.locale(), "dd. MMMM") + " - " + controller.end.toLocaleString(Qt.locale(), "dd. MMMM") } Kube.Label { visible: !controller.allDay text: controller.start.toLocaleString(Qt.locale(), "dd. MMMM hh:mm") + " - " + controller.end.toLocaleString(Qt.locale(), "dd. MMMM hh:mm") } Kube.Label { text: controller.description } Item { width: 1 height: Kube.Units.largeSpacing } RowLayout { Kube.Button { text: qsTr("Remove") onClicked: { root.controller.remove() } } Item { Layout.fillWidth: true } Kube.Button { text: qsTr("Edit") onClicked: { editor.open() } } } } } EventEditor { id: editor - //FIXME placing and hight - width: 800 - height: 300 + parent: ApplicationWindow.overlay + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height - height) / 2) controller: root.controller editMode: true } } diff --git a/views/calendar/qml/View.qml b/views/calendar/qml/View.qml index b8323b78..2c896915 100644 --- a/views/calendar/qml/View.qml +++ b/views/calendar/qml/View.qml @@ -1,334 +1,336 @@ /* * Copyright (C) 2018 Michael Bohlender, * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.9 import QtQuick.Controls 2 import QtQuick.Layouts 1.2 import org.kube.framework 1.0 as Kube import "dateutils.js" as DateUtils Kube.View { id: root property date currentDate: new Date() property date selectedDate: getFirstDayOfWeek(currentDate) property bool autoUpdateDate: true Kube.CheckedEntities { id: calendarFilterCollector } onSelectedDateChanged: { console.log("Selected date changed", selectedDate) } onRefresh: { Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "calendar"}) Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "event"}) } function getFirstDayOfWeek(date) { var firstDay = Qt.locale().firstDayOfWeek var year = date.getFullYear() var month = date.getMonth() //Jup, getDate returns the day of the month var day = date.getDate() while (true) { if (date.getDay() === firstDay) { return date } day = day - 1 date = new Date(year, month, day) } return date } function getFirstDayOfMonth(date) { var d = date d.setDate(1) return d } RowLayout { Timer { running: autoUpdateDate interval: 2000; repeat: true onTriggered: root.currentDate = new Date() } Rectangle { Layout.fillHeight: true width: Kube.Units.gridUnit * 10 color: Kube.Colors.darkBackgroundColor Column { id: topLayout anchors { top: parent.top left: parent.left right: parent.right margins: Kube.Units.largeSpacing } spacing: Kube.Units.largeSpacing Kube.PositiveButton { id: newEventButton objectName: "newEventButton" anchors { left: parent.left right: parent.right } focus: true text: qsTr("New Event") onClicked: { eventPopup.open() } } RowLayout { anchors { left: parent.left right: parent.right } spacing: Kube.Units.smallSpacing ButtonGroup { id: viewButtonGroup } Kube.TextButton { id: weekViewButton Layout.fillWidth: true text: qsTr("Week") textColor: Kube.Colors.highlightedTextColor checkable: true checked: true ButtonGroup.group: viewButtonGroup onCheckedChanged: { if (checked) { root.selectedDate = getFirstDayOfWeek(root.selectedDate) } } } Kube.TextButton { id: monthViewButton Layout.fillWidth: true text: qsTr("Month") textColor: Kube.Colors.highlightedTextColor checkable: true ButtonGroup.group: viewButtonGroup onCheckedChanged: { if (checked) { root.selectedDate = getFirstDayOfMonth(root.selectedDate) } } } } DateView { anchors { left: parent.left right: parent.right } date: root.currentDate MouseArea { anchors.fill: parent onClicked: root.selectedDate = root.currentDate } } DateSelector { id: dateSelector anchors { left: parent.left right: parent.right } selectedDate: root.selectedDate onSelected: { if (weekViewButton.checked) { root.selectedDate = getFirstDayOfWeek(date) } else { root.selectedDate = getFirstDayOfMonth(date) } } onNext: { if (weekViewButton.checked) { root.selectedDate = DateUtils.nextWeek(root.selectedDate) } else { root.selectedDate = DateUtils.nextMonth(root.selectedDate) } } onPrevious: { if (weekViewButton.checked) { root.selectedDate = DateUtils.previousWeek(root.selectedDate) } else { root.selectedDate = DateUtils.previousMonth(root.selectedDate) } } } } Kube.InlineAccountSwitcher { id: accountSwitcher //Grow from the button but don't go over topLayout anchors { bottom: statusBarContainer.top left: topLayout.left right: parent.right bottomMargin: Kube.Units.largeSpacing rightMargin: Kube.Units.largeSpacing } height: parent.height - (topLayout.y + topLayout.height) - Kube.Units.largeSpacing - anchors.bottomMargin - statusBarContainer.height delegate: Kube.ListView { id: listView spacing: Kube.Units.smallSpacing model: Kube.CheckableEntityModel { id: calendarModel type: "calendar" roles: ["name", "color", "enabled"] sortRole: "name" accountId: listView.parent.accountId checkedEntities: calendarFilterCollector } delegate: Kube.ListDelegate { id: delegate width: listView.availableWidth height: Kube.Units.gridUnit hoverEnabled: true background: Item {} RowLayout { anchors.fill: parent spacing: Kube.Units.smallSpacing Kube.CheckBox { id: checkBox opacity: 0.9 checked: model.checked || model.enabled onCheckedChanged: { model.checked = checked model.enabled = checked } indicator: Rectangle { width: Kube.Units.gridUnit * 0.8 height: Kube.Units.gridUnit * 0.8 color: model.color Rectangle { id: highlight anchors.fill: parent visible: checkBox.hovered || checkBox.visualFocus color: Kube.Colors.highlightColor opacity: 0.4 } Kube.Icon { anchors.centerIn: parent height: Kube.Units.gridUnit width: Kube.Units.gridUnit visible: checkBox.checked iconName: Kube.Icons.checkbox_inverted } } } Kube.Label { id: label Layout.fillWidth: true text: model.name color: Kube.Colors.highlightedTextColor elide: Text.ElideLeft clip: true } ToolTip { id: toolTipItem visible: delegate.hovered && label.truncated text: label.text } } } } } Item { id: statusBarContainer anchors { topMargin: Kube.Units.smallSpacing bottom: parent.bottom left: parent.left right: parent.right } height: childrenRect.height Rectangle { id: border visible: statusBar.visible anchors { right: parent.right left: parent.left margins: Kube.Units.smallSpacing } height: 1 color: Kube.Colors.viewBackgroundColor opacity: 0.3 } Kube.StatusBar { id: statusBar accountId: accountSwitcher.currentAccount height: Kube.Units.gridUnit * 2 anchors { top: border.bottom left: statusBarContainer.left right: statusBarContainer.right } } } } WeekView { visible: weekViewButton.checked Layout.fillHeight: true Layout.fillWidth: true currentDate: root.currentDate startDate: root.selectedDate calendarFilter: calendarFilterCollector.checkedEntities } MonthView { visible: monthViewButton.checked Layout.fillHeight: true Layout.fillWidth: true currentDate: root.currentDate startDate: getFirstDayOfWeek(getFirstDayOfMonth(root.selectedDate)) month: root.selectedDate.getMonth() calendarFilter: calendarFilterCollector.checkedEntities } } EventEditor { id: eventPopup x: root.width * 0.15 y: root.height * 0.15 width: root.width * 0.7 height: root.height * 0.7 + + start: root.selectedDate } } diff --git a/views/calendar/qml/dateutils.js b/views/calendar/qml/dateutils.js index 33d62ae1..12eb3a6a 100644 --- a/views/calendar/qml/dateutils.js +++ b/views/calendar/qml/dateutils.js @@ -1,74 +1,87 @@ /** * Returns the week number for this date. dowOffset is the day of week the week * "starts" on for your locale - it can be from 0 to 6. If dowOffset is 1 (Monday), * the week returned is the ISO 8601 week number. * @param int dowOffset * @return int */ function getWeek(date, dowOffset) { var newYear = new Date(date.getFullYear(),0,1); var day = newYear.getDay() - dowOffset; //the day of week the year begins on day = (day >= 0 ? day : day + 7); var daynum = Math.floor((date.getTime() - newYear.getTime() - (date.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1; var weeknum; //if the year starts before the middle of a week if(day < 4) { weeknum = Math.floor((daynum+day-1)/7) + 1; if(weeknum > 52) { var nYear = new Date(date.getFullYear() + 1,0,1); var nday = nYear.getDay() - dowOffset; nday = nday >= 0 ? nday : nday + 7; /*if the next year starts before the middle of the week, it is week #1 of that year*/ weeknum = nday < 4 ? 1 : 53; } } else { weeknum = Math.floor((daynum+day-1)/7); } return weeknum; } function roundToDay(date) { return new Date(date.getFullYear(), date.getMonth(), date.getDate()) } +function roundToMinutes(date, delta) { + var totalMinutes = date.getHours() * 60 + date.getMinutes() + //Round to nearest delta + totalMinutes = Math.round(totalMinutes / delta) * delta + var minutes = totalMinutes % 60 + var hours = (totalMinutes - minutes) / 60 + return new Date(date.getFullYear(), date.getMonth(), date.getDate(), hours, minutes, 0) +} + function addDaysToDate(date, days) { var date = new Date(date); date.setDate(date.getDate() + days); return date; } +function addMinutesToDate(date, minutes) { + return new Date(date.getTime() + minutes*60000); +} + function sameDay(date1, date2) { return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate() } function sameMonth(date1, date2) { return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() } function nextWeek(date) { var d = date d.setTime(date.getTime() + (24*60*60*1000) * 7); return d } function previousWeek(date) { var d = date d.setTime(date.getTime() - (24*60*60*1000) * 7); return d } function nextMonth(date) { var d = date d.setMonth(date.getMonth() + 1); return d } function previousMonth(date) { var d = date d.setMonth(date.getMonth() - 1); return d } diff --git a/views/calendar/tests/tst_calendar.qml b/views/calendar/tests/tst_calendar.qml index 20b78ba7..02357fcd 100644 --- a/views/calendar/tests/tst_calendar.qml +++ b/views/calendar/tests/tst_calendar.qml @@ -1,35 +1,35 @@ /* * Copyright 2017 Christian Mollekopf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library 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. */ import QtQuick 2.7 import QtTest 1.0 import org.kube.test 1.0 import "../qml" ViewTestCase { - name: "Todo" + name: "Calendar" View { id: view } function test_start() { verify(view) } } diff --git a/views/calendar/tests/tst_eventeditor.qml b/views/calendar/tests/tst_eventeditor.qml new file mode 100644 index 00000000..05b398f7 --- /dev/null +++ b/views/calendar/tests/tst_eventeditor.qml @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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. + */ + +import QtQuick 2.7 +import QtTest 1.0 +import org.kube.test 1.0 +import org.kube.framework 1.0 as Kube +import "../qml" + +ViewTestCase { + id: testCase + name: "EventEditor" + + Component { + id: editorComponent + EventEditor { + focus: true + } + } + Component { + id: controllerComponent + Kube.EventController { + } + } + + function test_1start() { + var editor = createTemporaryObject(editorComponent, testCase, {}) + verify(editor) + } + + function test_2loadStartDate() { + var start = new Date(2018, 1, 1, 11, 30, 0) + var editor = createTemporaryObject(editorComponent, testCase, {editMode: false, start: start}) + verify(editor) + var startDate = findChild(editor, "startDate"); + compare(startDate.dateTime, start) + + var endDate = findChild(editor, "endDate"); + compare(endDate.dateTime, start) + } + + function test_3loadControllerDates() { + var start = new Date(2018, 1, 1, 11, 30, 0) + var end = new Date(2018, 1, 1, 12, 0, 0) + var controller = createTemporaryObject(controllerComponent, testCase, {start: start, end: end, allDay: false}) + var editor = createTemporaryObject(editorComponent, testCase, {editMode: true, controller: controller}) + verify(editor) + + var startDate = findChild(editor, "startDate"); + compare(startDate.dateTime, start) + + var endDate = findChild(editor, "endDate"); + compare(endDate.dateTime, end) + } + + function test_4roundLoadedDates() { + var start = new Date(2018, 1, 1, 11, 33, 0) + var startRounded = new Date(2018, 1, 1, 11, 30, 0) + var end = new Date(2018, 1, 1, 11, 58, 0) + var endRounded = new Date(2018, 1, 1, 12, 0, 0) + var controller = createTemporaryObject(controllerComponent, testCase, {start: start, end: end, allDay: false}) + var editor = createTemporaryObject(editorComponent, testCase, {editMode: true, controller: controller}) + verify(editor) + + var startDate = findChild(editor, "startDate"); + compare(startDate.dateTime, startRounded) + + var endDate = findChild(editor, "endDate"); + compare(endDate.dateTime, endRounded) + } +}