summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRémi Nicole <nicole@kolabsystems.com>2018-04-17 14:30:27 +0200
committerChristian Mollekopf <chrigi_1@fastmail.fm>2018-04-17 14:32:29 +0200
commitb8d76329bced92d712185e5588dfd6061e8fe0f6 (patch)
treeaf37569d19c112549ab1e6ba0b94fe9c2d83c4fc
parentafaaeb6dc16315c78091653a2662be41781ddfd3 (diff)
downloadkube-b8d76329bced92d712185e5588dfd6061e8fe0f6.tar.gz
kube-b8d76329bced92d712185e5588dfd6061e8fe0f6.zip
Implement PeriodDayEventModel
Summary: Implements a model to load events. When finished, should fix T8225 TODO: - Use the parent / children model in qml, instead of relying on the ".events" attribute (using DelegateModel I think) Reviewers: cmollekopf Tags: #kube Maniphest Tasks: T8225 Differential Revision: https://phabricator.kde.org/D12089
-rw-r--r--framework/src/CMakeLists.txt2
-rw-r--r--framework/src/domain/perioddayeventmodel.cpp256
-rw-r--r--framework/src/domain/perioddayeventmodel.h129
-rw-r--r--framework/src/frameworkplugin.cpp2
-rw-r--r--views/calendar/qml/WeekEvents.qml95
-rw-r--r--views/calendar/qml/WeekView.qml12
6 files changed, 400 insertions, 96 deletions
diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt
index 0059bd00..a3c489b4 100644
--- a/framework/src/CMakeLists.txt
+++ b/framework/src/CMakeLists.txt
@@ -1,6 +1,7 @@
1 1
2find_package(Qt5 COMPONENTS REQUIRED Core Concurrent Quick Qml WebEngineWidgets Test WebEngine Gui) 2find_package(Qt5 COMPONENTS REQUIRED Core Concurrent Quick Qml WebEngineWidgets Test WebEngine Gui)
3find_package(KF5Mime 4.87.0 CONFIG REQUIRED) 3find_package(KF5Mime 4.87.0 CONFIG REQUIRED)
4find_package(KF5CalendarCore CONFIG REQUIRED)
4find_package(Sink 0.6.0 CONFIG REQUIRED) 5find_package(Sink 0.6.0 CONFIG REQUIRED)
5find_package(KAsync CONFIG REQUIRED) 6find_package(KAsync CONFIG REQUIRED)
6find_package(QGpgme CONFIG REQUIRED) 7find_package(QGpgme CONFIG REQUIRED)
@@ -16,6 +17,7 @@ add_library(kubeframework SHARED
16 settings/settings.cpp 17 settings/settings.cpp
17 domain/maillistmodel.cpp 18 domain/maillistmodel.cpp
18 domain/folderlistmodel.cpp 19 domain/folderlistmodel.cpp
20 domain/perioddayeventmodel.cpp
19 domain/composercontroller.cpp 21 domain/composercontroller.cpp
20 domain/modeltest.cpp 22 domain/modeltest.cpp
21 domain/retriever.cpp 23 domain/retriever.cpp
diff --git a/framework/src/domain/perioddayeventmodel.cpp b/framework/src/domain/perioddayeventmodel.cpp
new file mode 100644
index 00000000..637e5584
--- /dev/null
+++ b/framework/src/domain/perioddayeventmodel.cpp
@@ -0,0 +1,256 @@
1/*
2 Copyright (c) 2018 Michael Bohlender <michael.bohlender@kdemail.net>
3 Copyright (c) 2018 Christian Mollekopf <mollekopf@kolabsys.com>
4 Copyright (c) 2018 Rémi Nicole <minijackson@riseup.net>
5
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
20*/
21
22#include "perioddayeventmodel.h"
23
24#include <sink/log.h>
25#include <sink/query.h>
26#include <sink/store.h>
27
28#include <QJsonArray>
29#include <QJsonObject>
30#include <QMetaEnum>
31
32PeriodDayEventModel::PeriodDayEventModel(QObject *parent)
33 : QAbstractItemModel(parent), partitionedEvents(7)
34{
35 Sink::Query query;
36 query.setFlags(Sink::Query::LiveQuery);
37 query.request<Event::Summary>();
38 query.request<Event::Description>();
39 query.request<Event::StartTime>();
40 query.request<Event::EndTime>();
41
42 eventModel = Sink::Store::loadModel<Event>(query);
43
44 QObject::connect(eventModel.data(), &QAbstractItemModel::dataChanged, this, &PeriodDayEventModel::partitionData);
45 QObject::connect(eventModel.data(), &QAbstractItemModel::layoutChanged, this, &PeriodDayEventModel::partitionData);
46 QObject::connect(eventModel.data(), &QAbstractItemModel::modelReset, this, &PeriodDayEventModel::partitionData);
47 QObject::connect(eventModel.data(), &QAbstractItemModel::rowsInserted, this, &PeriodDayEventModel::partitionData);
48 QObject::connect(eventModel.data(), &QAbstractItemModel::rowsMoved, this, &PeriodDayEventModel::partitionData);
49 QObject::connect(eventModel.data(), &QAbstractItemModel::rowsRemoved, this, &PeriodDayEventModel::partitionData);
50
51 partitionData();
52}
53
54void PeriodDayEventModel::partitionData()
55{
56 SinkLog() << "Partitioning event data";
57
58 beginResetModel();
59
60 partitionedEvents = QVector<QList<QSharedPointer<Event>>>(mPeriodLength);
61
62 for (int i = 0; i < eventModel->rowCount(); ++i) {
63 auto event = eventModel->index(i, 0).data(Sink::Store::DomainObjectRole).value<Event::Ptr>();
64 QDate eventDate = event->getStartTime().date();
65
66 if (!eventDate.isValid()) {
67 SinkWarning() << "Invalid date in the eventModel, ignoring...";
68 continue;
69 }
70
71 int bucket = bucketOf(eventDate);
72
73 if (bucket >= 0) {
74 SinkTrace() << "Adding event:" << event->getSummary() << "in bucket #" << bucket;
75 partitionedEvents[bucket].append(event);
76 }
77 }
78
79 endResetModel();
80}
81
82int PeriodDayEventModel::bucketOf(const QDate &candidate) const
83{
84 int bucket = mPeriodStart.daysTo(candidate);
85 if (bucket >= mPeriodLength || bucket < 0) {
86 return -1;
87 }
88
89 return bucket;
90}
91
92QModelIndex PeriodDayEventModel::index(int row, int column, const QModelIndex &parent) const
93{
94 if (!hasIndex(row, column, parent)) {
95 return {};
96 }
97
98 if (!parent.isValid()) {
99 // Asking for a day
100
101 if (!(0 <= row && row < mPeriodLength)) {
102 return {};
103 }
104
105 return createIndex(row, column, DAY_ID);
106 }
107
108 // Asking for an Event
109 auto day = static_cast<int>(parent.row());
110
111 Q_ASSERT(0 <= day && day <= mPeriodLength);
112 if (row >= partitionedEvents[day].size()) {
113 return {};
114 }
115
116 return createIndex(row, column, day);
117}
118
119QModelIndex PeriodDayEventModel::parent(const QModelIndex &index) const
120{
121 if (!index.isValid()) {
122 return {};
123 }
124
125 if (index.internalId() == DAY_ID) {
126 return {};
127 }
128
129 auto day = index.internalId();
130
131 return this->index(day, 0);
132}
133
134int PeriodDayEventModel::rowCount(const QModelIndex &parent) const
135{
136 if (!parent.isValid()) {
137 return mPeriodLength;
138 }
139
140 auto day = parent.row();
141
142 return partitionedEvents[day].size();
143}
144
145int PeriodDayEventModel::columnCount(const QModelIndex &parent) const
146{
147 if (!parent.isValid()) {
148 return 1;
149 }
150
151 return eventModel->columnCount();
152}
153
154QVariant PeriodDayEventModel::data(const QModelIndex &id, int role) const
155{
156 if (id.internalId() == DAY_ID) {
157 auto day = id.row();
158
159 SinkTrace() << "Fetching data for day" << day << "with role"
160 << QMetaEnum::fromType<Roles>().valueToKey(role);
161
162 switch (role) {
163 case Qt::DisplayRole:
164 return mPeriodStart.addDays(day).toString();
165 case Events: {
166 auto result = QVariantList{};
167
168 for (int i = 0; i < partitionedEvents[day].size(); ++i) {
169 auto eventId = index(i, 0, id);
170 SinkTrace() << "Appending event:" << data(eventId, Summary);
171
172 auto startTime = data(eventId, StartTime).toDateTime().time();
173
174 result.append(QVariantMap{
175 {"text", data(eventId, Summary)},
176 {"description", data(eventId, Description)},
177 {"starts", startTime.hour() + startTime.minute() / 60.},
178 {"duration", data(eventId, Duration)},
179 {"color", "#134bab"},
180 {"indention", 0},
181 });
182 }
183
184 return result;
185 }
186 default:
187 SinkWarning() << "Unknown role for day:" << QMetaEnum::fromType<Roles>().valueToKey(role);
188 return {};
189 }
190 } else {
191 auto day = id.internalId();
192 SinkTrace() << "Fetching data for event on day" << day << "with role"
193 << QMetaEnum::fromType<Roles>().valueToKey(role);
194 auto event = partitionedEvents[day].at(id.row());
195
196 switch (role) {
197 case Summary:
198 return event->getSummary();
199 case Description:
200 return event->getDescription();
201 case StartTime:
202 return event->getStartTime();
203 case Duration: {
204 auto start = event->getStartTime();
205 auto end = event->getEndTime();
206 return start.secsTo(end) / 3600;
207 }
208 default:
209 SinkWarning() << "Unknown role for event:" << QMetaEnum::fromType<Roles>().valueToKey(role);
210 return {};
211 }
212 }
213}
214
215QHash<int, QByteArray> PeriodDayEventModel::roleNames() const
216{
217 return {
218 {Events, "events"},
219 {Summary, "summary"},
220 {Description, "description"},
221 {StartTime, "starts"},
222 {Duration, "duration"},
223 };
224}
225
226QDate PeriodDayEventModel::periodStart() const
227{
228 return mPeriodStart;
229}
230
231void PeriodDayEventModel::setPeriodStart(const QDate &start)
232{
233 if (!start.isValid()) {
234 SinkWarning() << "Passed an invalid starting date in setPeriodStart, ignoring...";
235 return;
236 }
237
238 mPeriodStart = start;
239 partitionData();
240}
241
242void PeriodDayEventModel::setPeriodStart(const QVariant &start)
243{
244 setPeriodStart(start.toDate());
245}
246
247int PeriodDayEventModel::periodLength() const
248{
249 return mPeriodLength;
250}
251
252void PeriodDayEventModel::setPeriodLength(int length)
253{
254 mPeriodLength = length;
255 partitionData();
256}
diff --git a/framework/src/domain/perioddayeventmodel.h b/framework/src/domain/perioddayeventmodel.h
new file mode 100644
index 00000000..e116d9a2
--- /dev/null
+++ b/framework/src/domain/perioddayeventmodel.h
@@ -0,0 +1,129 @@
1/*
2 Copyright (c) 2018 Michael Bohlender <michael.bohlender@kdemail.net>
3 Copyright (c) 2018 Christian Mollekopf <mollekopf@kolabsys.com>
4 Copyright (c) 2018 Rémi Nicole <minijackson@riseup.net>
5
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
20*/
21
22#pragma once
23
24#include <sink/applicationdomaintype.h>
25
26#include <QAbstractItemModel>
27#include <QList>
28#include <QSharedPointer>
29#include <QVector>
30
31#include <limits>
32
33// Facility used to get a restricted period into a Sink model comprised of
34// events, partitioned according to the day the events take place.
35//
36// Model Format
37// ============
38//
39// Day 0
40// |--- Event 0 starting at `periodStart + 0d`
41// |--- Event 1 starting at `periodStart + 0d`
42// '--- Event 2 starting at `periodStart + 0d`
43// Day 1
44// '--- Event 0 starting at `periodStart + 1d`
45// Day 2
46// Day 3
47// |--- Event 0 starting at `periodStart + 3d`
48// '--- Event 1 starting at `periodStart + 3d`
49// Day 4
50// ⋮
51//
52// Implementation notes
53// ====================
54//
55// On the model side
56// -----------------
57//
58// Columns are never used.
59//
60// Top-level items just contains the ".events" attribute, and their rows
61// correspond to their offset compared to the start of the period (in number of
62// days). In that case the internalId contains DAY_ID.
63//
64// Direct children are events, and their rows corresponds to their index in
65// their partition. In that case no internalId / internalPointer is used.
66//
67// Internally:
68// -----------
69//
70// On construction and on dataChanged, all events are processed and partitioned
71// in partitionedEvents:
72//
73// QVector< QList<QSharedPointer<Event> >
74// ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
75// | |
76// | '--- List of event pointers for that day
77// '--- Partition / day
78//
79class PeriodDayEventModel : public QAbstractItemModel
80{
81 Q_OBJECT
82
83 Q_PROPERTY(QVariant start READ periodStart WRITE setPeriodStart)
84 Q_PROPERTY(int length READ periodLength WRITE setPeriodLength)
85
86public:
87 using Event = Sink::ApplicationDomain::Event;
88
89 enum Roles
90 {
91 Events = Qt::UserRole + 1,
92 Summary,
93 Description,
94 StartTime,
95 Duration,
96 };
97 Q_ENUM(Roles);
98 PeriodDayEventModel(QObject *parent = nullptr);
99 ~PeriodDayEventModel() = default;
100
101 QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override;
102 QModelIndex parent(const QModelIndex &index) const override;
103
104 int rowCount(const QModelIndex &parent) const override;
105 int columnCount(const QModelIndex &parent) const override;
106
107 QVariant data(const QModelIndex &index, int role) const override;
108
109 QHash<int, QByteArray> roleNames() const override;
110
111 QDate periodStart() const;
112 void setPeriodStart(const QDate &);
113 void setPeriodStart(const QVariant &);
114 int periodLength() const;
115 void setPeriodLength(int);
116
117private:
118 void partitionData();
119
120 int bucketOf(const QDate &candidate) const;
121
122 QDate mPeriodStart;
123 int mPeriodLength = 7;
124
125 QSharedPointer<QAbstractItemModel> eventModel;
126 QVector<QList<QSharedPointer<Event>>> partitionedEvents;
127
128 static const constexpr quintptr DAY_ID = std::numeric_limits<quintptr>::max();
129};
diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp
index d512ce10..2eb53237 100644
--- a/framework/src/frameworkplugin.cpp
+++ b/framework/src/frameworkplugin.cpp
@@ -22,6 +22,7 @@
22 22
23#include "domain/maillistmodel.h" 23#include "domain/maillistmodel.h"
24#include "domain/folderlistmodel.h" 24#include "domain/folderlistmodel.h"
25#include "domain/perioddayeventmodel.h"
25#include "domain/composercontroller.h" 26#include "domain/composercontroller.h"
26#include "domain/mime/messageparser.h" 27#include "domain/mime/messageparser.h"
27#include "domain/retriever.h" 28#include "domain/retriever.h"
@@ -120,6 +121,7 @@ void FrameworkPlugin::registerTypes (const char *uri)
120{ 121{
121 qmlRegisterType<FolderListModel>(uri, 1, 0, "FolderListModel"); 122 qmlRegisterType<FolderListModel>(uri, 1, 0, "FolderListModel");
122 qmlRegisterType<MailListModel>(uri, 1, 0, "MailListModel"); 123 qmlRegisterType<MailListModel>(uri, 1, 0, "MailListModel");
124 qmlRegisterType<PeriodDayEventModel>(uri, 1, 0, "PeriodDayEventModel");
123 qmlRegisterType<ComposerController>(uri, 1, 0, "ComposerController"); 125 qmlRegisterType<ComposerController>(uri, 1, 0, "ComposerController");
124 qmlRegisterType<Kube::ControllerAction>(uri, 1, 0, "ControllerAction"); 126 qmlRegisterType<Kube::ControllerAction>(uri, 1, 0, "ControllerAction");
125 qmlRegisterType<MessageParser>(uri, 1, 0, "MessageParser"); 127 qmlRegisterType<MessageParser>(uri, 1, 0, "MessageParser");
diff --git a/views/calendar/qml/WeekEvents.qml b/views/calendar/qml/WeekEvents.qml
index 005fb19e..774f254e 100644
--- a/views/calendar/qml/WeekEvents.qml
+++ b/views/calendar/qml/WeekEvents.qml
@@ -1,93 +1,8 @@
1import QtQuick 2.7 1import QtQuick 2.7
2 2
3ListModel { 3import org.kube.framework 1.0 as Kube
4 ListElement { 4
5 events: [ 5Kube.PeriodDayEventModel {
6 ListElement { 6 start: "2018-04-09"
7 color: "#af1a6a" 7 length: 7
8 starts: 1
9 duration: 4
10 text: "Meeting"
11 indention: 0
12 },
13 ListElement {
14 color: "#134bab"
15 starts: 9
16 duration: 5
17 text: "Sport"
18 indention: 0
19 }
20 ]
21 }
22 ListElement {
23 events: [
24 ListElement {
25 color: "#134bab"
26 starts: 9
27 duration: 5
28 text: "Sport"
29 indention: 0
30 }
31 ]
32 }
33 ListElement {
34 events: []
35 }
36 ListElement {
37 events: [
38 ListElement {
39 color: "#af1a6a"
40 starts: 1
41 duration: 4
42 indention: 0
43 text: "Meeting"
44 }
45 ]
46 }
47 ListElement {
48 events: [
49 ListElement {
50 color: "#134bab"
51 starts: 3
52 duration: 5
53 indention: 0
54 text: "Meeting"
55 },
56 ListElement {
57 color: "#af1a6a"
58 starts: 4
59 duration: 7
60 indention: 1
61 text: "Meeting2"
62 }
63 ]
64 }
65 ListElement {
66 events: [
67 ListElement {
68 color: "#134bab"
69 starts: 8
70 duration: 5
71 indention: 0
72 text: "Meeting"
73 },
74 ListElement {
75 color: "#af1a6a"
76 starts: 8
77 duration: 4
78 indention: 1
79 text: "Meeting2"
80 },
81 ListElement {
82 color: "#af1a6a"
83 starts: 9
84 duration: 7
85 indention: 2
86 text: "Meeting2"
87 }
88 ]
89 }
90 ListElement {
91 events: []
92 }
93} 8}
diff --git a/views/calendar/qml/WeekView.qml b/views/calendar/qml/WeekView.qml
index 8eef3a92..877500c5 100644
--- a/views/calendar/qml/WeekView.qml
+++ b/views/calendar/qml/WeekView.qml
@@ -212,12 +212,12 @@ FocusScope {
212 right: parent.right 212 right: parent.right
213 rightMargin: Kube.Units.smallSpacing 213 rightMargin: Kube.Units.smallSpacing
214 } 214 }
215 width: Kube.Units.gridUnit * 7 - Kube.Units.smallSpacing * 2 - Kube.Units.gridUnit * model.indention 215 width: Kube.Units.gridUnit * 7 - Kube.Units.smallSpacing * 2 - Kube.Units.gridUnit * model.modelData.indention
216 height: Kube.Units.gridUnit * model.duration 216 height: Kube.Units.gridUnit * model.modelData.duration
217 y: Kube.Units.gridUnit * model.starts 217 y: Kube.Units.gridUnit * model.modelData.starts
218 x: Kube.Units.gridUnit * model.indention 218 x: Kube.Units.gridUnit * model.modelData.indention
219 219
220 color: model.color 220 color: model.modelData.color
221 border.width: 1 221 border.width: 1
222 border.color: Kube.Colors.viewBackgroundColor 222 border.color: Kube.Colors.viewBackgroundColor
223 223
@@ -226,7 +226,7 @@ FocusScope {
226 left: parent.left 226 left: parent.left
227 leftMargin: Kube.Units.smallSpacing 227 leftMargin: Kube.Units.smallSpacing
228 } 228 }
229 text: model.text 229 text: model.modelData.text
230 color: Kube.Colors.highlightedTextColor 230 color: Kube.Colors.highlightedTextColor
231 } 231 }
232 232