diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt --- a/framework/src/CMakeLists.txt +++ b/framework/src/CMakeLists.txt @@ -1,6 +1,7 @@ find_package(Qt5 COMPONENTS REQUIRED Core Concurrent Quick Qml WebEngineWidgets Test WebEngine Gui) find_package(KF5Mime 4.87.0 CONFIG REQUIRED) +find_package(KF5CalendarCore CONFIG REQUIRED) find_package(Sink 0.6.0 CONFIG REQUIRED) find_package(KAsync CONFIG REQUIRED) find_package(QGpgme CONFIG REQUIRED) @@ -16,6 +17,7 @@ settings/settings.cpp domain/maillistmodel.cpp domain/folderlistmodel.cpp + domain/eventtreemodel.cpp domain/composercontroller.cpp domain/modeltest.cpp domain/retriever.cpp diff --git a/framework/src/domain/eventtreemodel.h b/framework/src/domain/eventtreemodel.h new file mode 100644 --- /dev/null +++ b/framework/src/domain/eventtreemodel.h @@ -0,0 +1,97 @@ +/* + Copyright (c) 2018 Michael Bohlender + Copyright (c) 2018 Christian Mollekopf + Copyright (c) 2018 Rémi Nicole + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +// Implementation notes +// ==================== +// +// On the model side +// ----------------- +// +// Columns are never used. +// +// Top-level items just contains the ".events" attribute, and their rows +// correspond to their day of week. In that case the internalId contains +// DAY_ID. +// +// Direct children are events, and their rows corresponds to their index in +// their partition. In that case no internalId / internalPointer is used. +// +// Internally: +// ----------- +// +// On construction and on dataChanged, all events are processed and partitioned +// in partitionedEvents: +// +// QVector< QList > +// ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// | | +// | '--- List of event pointers for that day +// '--- Partition / day +// +class EventTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + using Event = Sink::ApplicationDomain::Event; + + enum Roles + { + Events = Qt::UserRole + 1, + Summary, + Description, + StartTime, + Duration, + }; + Q_ENUM(Roles); + EventTreeModel(QObject *parent = nullptr); + ~EventTreeModel() = default; + + QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override; + QModelIndex parent(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + + QVariant data(const QModelIndex &index, int role) const override; + + QHash roleNames() const override; + +private: + void partitionData(); + + QSharedPointer eventModel; + QVector>> partitionedEvents; + + QMutex partitioningMutex; + + static const constexpr quintptr DAY_ID = 7; +}; diff --git a/framework/src/domain/eventtreemodel.cpp b/framework/src/domain/eventtreemodel.cpp new file mode 100644 --- /dev/null +++ b/framework/src/domain/eventtreemodel.cpp @@ -0,0 +1,233 @@ +/* + Copyright (c) 2018 Michael Bohlender + Copyright (c) 2018 Christian Mollekopf + Copyright (c) 2018 Rémi Nicole + + This library 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 of the License, or (at your + option) any later version. + + This library 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 library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "eventtreemodel.h" + +#include +#include +#include + +#include +#include +#include + +EventTreeModel::EventTreeModel(QObject *parent) : QAbstractItemModel(parent), partitionedEvents(7) +{ + Sink::Query query; + query.setFlags(Sink::Query::LiveQuery); + query.request(); + query.request(); + query.request(); + query.request(); + + eventModel = Sink::Store::loadModel(query); + + QObject::connect(eventModel.data(), &QAbstractItemModel::dataChanged, this, &EventTreeModel::partitionData); + QObject::connect(eventModel.data(), &QAbstractItemModel::layoutChanged, this, &EventTreeModel::partitionData); + QObject::connect(eventModel.data(), &QAbstractItemModel::modelReset, this, &EventTreeModel::partitionData); + QObject::connect(eventModel.data(), &QAbstractItemModel::rowsInserted, this, &EventTreeModel::partitionData); + QObject::connect(eventModel.data(), &QAbstractItemModel::rowsMoved, this, &EventTreeModel::partitionData); + QObject::connect(eventModel.data(), &QAbstractItemModel::rowsRemoved, this, &EventTreeModel::partitionData); + + partitionData(); +} + +void EventTreeModel::partitionData() +{ + partitioningMutex.lock(); + + SinkLog() << "Partitioning event data"; + + beginResetModel(); + + for (int i = 0; i < 7; ++i) { + partitionedEvents[i].clear(); + } + + for (int i = 0; i < eventModel->rowCount(); ++i) { + auto event = eventModel->index(i, 0).data(Sink::Store::DomainObjectRole).value(); + int dayOfWeek = event->getStartTime().date().dayOfWeek(); + + Q_ASSERT(dayOfWeek != 0); + dayOfWeek -= 1; + + SinkTrace() << "Adding event:" << event->getSummary() << "in bucket #" << dayOfWeek; + + partitionedEvents[dayOfWeek].append(event); + } + + endResetModel(); + + partitioningMutex.unlock(); +} + +QModelIndex EventTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return {}; + } + + if (!parent.isValid()) { + // Asking for a LocalEventListModel + + // TODO: Assuming week view + if (!(0 <= row && row <= 6)) { + return {}; + } + + return createIndex(row, column, DAY_ID); + } + + // Asking for an Event + auto day = static_cast(parent.row()); + + Q_ASSERT(0 <= day && day <= 6); + if (row >= partitionedEvents[day].size()) { + return {}; + } + + return createIndex(row, column, day); +} + +QModelIndex EventTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) { + return {}; + } + + if (index.internalId() == DAY_ID) { + return {}; + } + + auto day = index.internalId(); + + return this->index(day, 0); +} + +int EventTreeModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return 7; + } + + auto day = parent.row(); + + return partitionedEvents[day].size(); +} + +int EventTreeModel::columnCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return 1; + } + + return eventModel->columnCount(); +} + +QVariant EventTreeModel::data(const QModelIndex &id, int role) const +{ + if (id.internalId() == DAY_ID) { + auto day = id.row(); + + SinkTrace() << "Fetching data for day" << day << "with role" << QMetaEnum::fromType().valueToKey(role); + + switch (role) { + case Qt::DisplayRole: + switch (day) { + case 0: + return "Monday"; + case 1: + return "Tuesday"; + case 2: + return "Wednesday"; + case 3: + return "Thursday"; + case 4: + return "Friday"; + case 5: + return "Saturday"; + case 6: + return "Sunday"; + default: + SinkWarning() << "Unknown day"; + return {}; + } + case Events: { + auto result = QVariantList{}; + + for (int i = 0; i < partitionedEvents[day].size(); ++i) { + auto eventId = index(i, 0, id); + SinkTrace() << "Appending event:" << data(eventId, Summary); + + auto startTime = data(eventId, StartTime).toDateTime().time(); + + result.append(QVariantMap{ + {"text", data(eventId, Summary)}, + {"description", data(eventId, Description)}, + {"starts", startTime.hour() + startTime.minute() / 60.}, + {"duration", data(eventId, Duration)}, + {"color", "#134bab"}, + {"indention", 0}, + }); + } + + return result; + } + default: + SinkWarning() << "Unknown role for day:" << QMetaEnum::fromType().valueToKey(role); + return {}; + } + } else { + auto day = id.internalId(); + SinkTrace() << "Fetching data for event on day" << day << "with role" << QMetaEnum::fromType().valueToKey(role); + auto event = partitionedEvents[day].at(id.row()); + + switch (role) { + case Summary: + return event->getSummary(); + case Description: + return event->getDescription(); + case StartTime: + return event->getStartTime(); + case Duration: { + auto start = event->getStartTime(); + auto end = event->getEndTime(); + return start.secsTo(end) / 3600; + } + default: + SinkWarning() << "Unknown role for event:" << QMetaEnum::fromType().valueToKey(role); + return {}; + } + } +} + +QHash EventTreeModel::roleNames() const +{ + QHash roles; + + roles[Events] = "events"; + roles[Summary] = "summary"; + roles[Description] = "description"; + roles[StartTime] = "starts"; + roles[Duration] = "duration"; + + return roles; +} diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp --- a/framework/src/frameworkplugin.cpp +++ b/framework/src/frameworkplugin.cpp @@ -22,6 +22,7 @@ #include "domain/maillistmodel.h" #include "domain/folderlistmodel.h" +#include "domain/eventtreemodel.h" #include "domain/composercontroller.h" #include "domain/mime/messageparser.h" #include "domain/retriever.h" @@ -120,6 +121,7 @@ { qmlRegisterType(uri, 1, 0, "FolderListModel"); qmlRegisterType(uri, 1, 0, "MailListModel"); + qmlRegisterType(uri, 1, 0, "EventTreeModel"); qmlRegisterType(uri, 1, 0, "ComposerController"); qmlRegisterType(uri, 1, 0, "ControllerAction"); qmlRegisterType(uri, 1, 0, "MessageParser"); diff --git a/views/calendar/qml/WeekEvents.qml b/views/calendar/qml/WeekEvents.qml --- a/views/calendar/qml/WeekEvents.qml +++ b/views/calendar/qml/WeekEvents.qml @@ -1,93 +1,6 @@ import QtQuick 2.7 -ListModel { - ListElement { - events: [ - ListElement { - color: "#af1a6a" - starts: 1 - duration: 4 - text: "Meeting" - indention: 0 - }, - ListElement { - color: "#134bab" - starts: 9 - duration: 5 - text: "Sport" - indention: 0 - } - ] - } - ListElement { - events: [ - ListElement { - color: "#134bab" - starts: 9 - duration: 5 - text: "Sport" - indention: 0 - } - ] - } - ListElement { - events: [] - } - ListElement { - events: [ - ListElement { - color: "#af1a6a" - starts: 1 - duration: 4 - indention: 0 - text: "Meeting" - } - ] - } - ListElement { - events: [ - ListElement { - color: "#134bab" - starts: 3 - duration: 5 - indention: 0 - text: "Meeting" - }, - ListElement { - color: "#af1a6a" - starts: 4 - duration: 7 - indention: 1 - text: "Meeting2" - } - ] - } - ListElement { - events: [ - ListElement { - color: "#134bab" - starts: 8 - duration: 5 - indention: 0 - text: "Meeting" - }, - ListElement { - color: "#af1a6a" - starts: 8 - duration: 4 - indention: 1 - text: "Meeting2" - }, - ListElement { - color: "#af1a6a" - starts: 9 - duration: 7 - indention: 2 - text: "Meeting2" - } - ] - } - ListElement { - events: [] - } +import org.kube.framework 1.0 as Kube + +Kube.EventTreeModel { } diff --git a/views/calendar/qml/WeekView.qml b/views/calendar/qml/WeekView.qml --- a/views/calendar/qml/WeekView.qml +++ b/views/calendar/qml/WeekView.qml @@ -212,12 +212,12 @@ right: parent.right rightMargin: Kube.Units.smallSpacing } - width: Kube.Units.gridUnit * 7 - Kube.Units.smallSpacing * 2 - Kube.Units.gridUnit * model.indention - height: Kube.Units.gridUnit * model.duration - y: Kube.Units.gridUnit * model.starts - x: Kube.Units.gridUnit * model.indention + width: Kube.Units.gridUnit * 7 - Kube.Units.smallSpacing * 2 - Kube.Units.gridUnit * model.modelData.indention + height: Kube.Units.gridUnit * model.modelData.duration + y: Kube.Units.gridUnit * model.modelData.starts + x: Kube.Units.gridUnit * model.modelData.indention - color: model.color + color: model.modelData.color border.width: 1 border.color: Kube.Colors.viewBackgroundColor @@ -226,7 +226,7 @@ left: parent.left leftMargin: Kube.Units.smallSpacing } - text: model.text + text: model.modelData.text color: Kube.Colors.highlightedTextColor }