createTimeGroups(): use QDateTime instead of QTime to avoid "midnight overflow".
[toast/confclerk.git] / src / mvc / eventmodel.cpp
1 /*
2  * Copyright (C) 2010 Ixonos Plc.
3  * Copyright (C) 2011-2012 Philipp Spitzer, gregor herrmann
4  *
5  * This file is part of ConfClerk.
6  *
7  * ConfClerk is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation, either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * ConfClerk is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * ConfClerk.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 #include "eventmodel.h"
21 #include <conference.h>
22 #include <track.h>
23 #include <room.h>
24
25 const QString EventModel::COMMA_SEPARATOR = ", ";
26
27 EventModel::EventModel()
28 { }
29
30
31 void EventModel::Group::setTitle(const QList<Event>& mEvents) {
32     QTime startTime = mEvents.at(mFirstEventIndex).start().time();
33     QTime endTime(0, 0);
34     for (int i = mFirstEventIndex; i != mFirstEventIndex + mChildCount; ++i) {
35         endTime = qMax(mEvents.at(i).start().time().addSecs(mEvents.at(i).duration()), endTime);
36     }
37     mTitle = QString("%1 - %2").arg(startTime.toString("HH:mm")).arg(endTime.toString("HH:mm"));
38 }
39
40
41 // We want to group the events into "time slots/time groups" that
42 // should start at full hours and have the duration of either
43 // one hour or (if less than 3 events are in one time slot)
44 // multiple of one hour.
45 void EventModel::createTimeGroups()
46 {
47     mGroups.clear();
48     mParents.clear();
49     if (mEvents.empty()) return;
50
51     const int minTimeSpan = 3600; // one hour
52     const int minChildCount = 3; // minimum number of events in one group
53
54     // Create the first time group. The events have to be sorted by start time at this point!
55     QDateTime groupStartDateTime(mEvents.first().start().date(), mEvents.first().start().time());
56     QDateTime groupEndDateTime = groupStartDateTime.addSecs(mEvents.first().duration());
57     mGroups << EventModel::Group("", 0);
58     int timeSpan = minTimeSpan;
59
60     for (int i = 0; i != mEvents.count(); ++i) {
61         QDateTime eventStartDateTime (mEvents.at(i).start().date(), mEvents.at(i).start().time());
62         QDateTime eventEndDateTime = eventStartDateTime.addSecs(mEvents.at(i).duration());
63
64         if (eventStartDateTime >= groupStartDateTime.addSecs(timeSpan)) {
65             // a new group could be necessary
66             if (mGroups.last().mChildCount < minChildCount) {
67                 // too few events in the group => no new group
68                 // except a gap in time would occur that is longer than minTimeSpan
69                 QDateTime prevEventStartDateTime (mEvents.at(i).start().date(), mEvents.at(i).start().time());
70                 if (i > 0 && qMax(prevEventStartDateTime.addSecs(mEvents.at(i-1).duration()), groupEndDateTime).secsTo(eventStartDateTime) < minTimeSpan) {
71                     timeSpan += minTimeSpan;
72                     --i;
73                     continue; // repeat with the same event
74                 }
75             }
76
77             // a new group is necessary
78             mGroups.last().setTitle(mEvents);
79             groupStartDateTime = groupStartDateTime.addSecs(timeSpan);
80             groupEndDateTime = groupStartDateTime.addSecs(mEvents.at(i).duration());
81             mGroups << EventModel::Group("", i);
82             timeSpan = minTimeSpan;
83         }
84
85         // insert event into current group
86         mParents[mEvents.at(i).id()] = mGroups.count() - 1;
87         mGroups.last().mChildCount += 1;
88         groupEndDateTime = qMax(eventEndDateTime, groupEndDateTime);
89     }
90
91     // the last group needs a title as well
92     mGroups.last().setTitle(mEvents);
93
94     reset();
95 }
96
97 void EventModel::createTrackGroups() {
98     mGroups.clear();
99     mParents.clear();
100     if (mEvents.empty())
101     {
102         return;
103     }
104     int trackId = mEvents.first().trackId();
105
106     mGroups << EventModel::Group(Track::retrieveTrackName(trackId), 0);
107     int nextTrackId = trackId;
108
109     for (int i=0; i<mEvents.count(); i++)
110     {
111         trackId = mEvents.at(i).trackId();
112         if (nextTrackId != trackId)
113         {
114             mGroups.last().mChildCount = i - mGroups.last().mFirstEventIndex;
115             mGroups << EventModel::Group(Track::retrieveTrackName(trackId), i);
116             nextTrackId = trackId;
117         }
118         // add parent-child relation
119         mParents[mEvents.at(i).id()] = mGroups.count() - 1;
120     }
121     mGroups.last().mChildCount = mEvents.count() - mGroups.last().mFirstEventIndex;
122 }
123
124 void EventModel::createRoomGroups()
125 {
126     mGroups.clear();
127     mParents.clear();
128     if (mEvents.empty())
129     {
130         return;
131     }
132     int roomId = mEvents.first().roomId();
133
134     mGroups << EventModel::Group(Room::retrieveRoomName(roomId), 0);
135     int nextRoomId = roomId;
136
137     QList<Event>::iterator event = mEvents.begin();
138     int i = 0;
139     while (event != mEvents.end())
140     {
141         roomId = event->roomId();
142         if (nextRoomId != roomId)
143         {
144             mGroups.last().mChildCount = i - mGroups.last().mFirstEventIndex;
145             mGroups << EventModel::Group(Room::retrieveRoomName(roomId), i);
146             nextRoomId = roomId;
147         }
148         mParents[event->id()] = mGroups.count() - 1;
149         event++;
150         i++;
151     }
152     mGroups.last().mChildCount = mEvents.count() - mGroups.last().mFirstEventIndex;
153 }
154
155 QVariant EventModel::data(const QModelIndex& index, int role) const
156 {
157     if (index.isValid() && role == Qt::DisplayRole)
158     {
159         if (index.internalId() == 0)
160         {
161             return mGroups.at(index.row()).mTitle;
162         }
163         else //event data
164         {
165             return static_cast<Event*>(index.internalPointer())->id();
166         }
167     }
168
169     return QVariant();
170 }
171
172 QModelIndex EventModel::index(int row, int column, const QModelIndex& parent) const
173 {
174     // TODO: add checks for out of range rows
175
176     if (!parent.isValid())
177     {
178         return createIndex(row, column, 0);
179     }
180     else if (parent.internalId() == 0)
181     {
182         const Group& group = mGroups.at(parent.row());
183         Event* event = const_cast<Event*>(&mEvents.at(row + group.mFirstEventIndex));
184         return createIndex(row, column, reinterpret_cast<void*>(event));
185     }
186     else
187     {
188         return QModelIndex();
189     }
190 }
191
192 QModelIndex EventModel::parent(const QModelIndex & index) const
193 {
194     if (index.isValid())
195     {
196         if (index.internalId() == 0)
197         {
198             return QModelIndex();
199         }
200
201         Event * event = static_cast<Event*>(index.internalPointer());
202
203         return createIndex(mParents[event->id()], 0, 0);
204     }
205
206     return QModelIndex();
207 }
208
209 int EventModel::columnCount(const QModelIndex & parent) const
210 {
211     Q_UNUSED(parent);
212     return 1;
213 }
214
215 int EventModel::rowCount (const QModelIndex & parent) const
216 {
217     if (!parent.isValid())
218     {
219         return mGroups.count();
220     }
221
222     if (parent.internalId() == 0)
223     {
224         return mGroups.at(parent.row()).mChildCount;
225     }
226
227     return 0;
228 }
229
230 void EventModel::clearModel()
231 {
232     mGroups.clear();
233     mEvents.clear();
234     mParents.clear();
235
236     reset();
237 }
238
239 void EventModel::loadEvents(const QDate &aDate, int aConferenceId)
240 {
241     clearModel();
242     // check for existence of the conference in the DB
243     if(Conference::getAll().count())
244     {
245         mEvents = Event::getByDate(QDate(aDate.year(), aDate.month(), aDate.day()), aConferenceId, "start, duration");
246     }
247     createTimeGroups();
248 }
249
250 void EventModel::loadFavEvents(const QDate &aDate, int aConferenceId)
251 {
252     clearModel();
253     // check for existence of the conference in the DB
254     if(Conference::getAll().count())
255     {
256         mEvents = Event::getFavByDate(QDate(aDate.year(), aDate.month(), aDate.day()), aConferenceId);
257     }
258     createTimeGroups();
259 }
260
261 int EventModel::loadSearchResultEvents(const QDate &aDate, int aConferenceId)
262 {
263     clearModel();
264     // check for existence of the conference in the DB
265     if(Conference::getAll().count())
266     {
267         try{
268             mEvents = Event::getSearchResultByDate(QDate(aDate.year(), aDate.month(), aDate.day()), aConferenceId, "start, duration");
269         }
270         catch( OrmException &e  ){
271             qDebug() << "Event::getSearchResultByDate failed: " << e.text();
272         }
273         catch(...){
274             qDebug() << "Event::getSearchResultByDate failed";
275         }
276
277     }
278
279     createTimeGroups();
280
281     return mEvents.count();
282 }
283
284 void EventModel::loadEventsByTrack(const QDate &aDate, int aConferenceId)
285 {
286     clearModel();
287     if (Conference::getAll().count())
288     {
289         mEvents = Event::getByDate(QDate(aDate.year(), aDate.month(), aDate.day()), aConferenceId, "xid_track, start, duration");
290     }
291     createTrackGroups();
292 }
293
294 void EventModel::loadEventsByRoom(const QDate &aDate, int aConferenceId)
295 {
296     clearModel();
297     if (Conference::getAll().count())
298     {
299         mEvents = Event::getByDateAndRoom(QDate(aDate.year(), aDate.month(), aDate.day()), aConferenceId);
300     }
301     createRoomGroups();
302 }
303
304
305 void EventModel::loadConflictEvents(int aEventId, int aConferenceId) {
306     clearModel();
307     // check for existence of the conference in the DB
308     if(Conference::getAll().count())
309     {
310         mEvents = Event::conflictEvents(aEventId, aConferenceId);
311     }
312     createTimeGroups();
313 }
314
315 void EventModel::updateModel(int aEventId)
316 {
317     for(int i=0; i<mEvents.count(); i++)
318     {
319         if(mEvents[i].id() == aEventId)
320             mEvents[i] = Event::getById(aEventId,Conference::activeConference());
321     }
322
323     // find the ModelIndex for given aEventId
324     for(int i=0; i<mGroups.count(); i++)
325     {
326         QModelIndex groupIndex = index(i,0,QModelIndex());
327         for(int j=0; j<mGroups[i].mChildCount; j++)
328         {
329             QModelIndex eventIndex = index(j,0,groupIndex);
330             if(static_cast<Event*>(eventIndex.internalPointer())->id() == aEventId)
331             {
332                 emit(dataChanged(groupIndex,groupIndex));
333                 emit(dataChanged(eventIndex,eventIndex));
334             }
335         }
336     }
337 }
338