1 | /* |
2 | This file is part of the kcalcore library. |
3 | |
4 | Copyright (C) 2013 Christian Mollekopf <mollekopf@kolabsys.com> |
5 | |
6 | This library is free software; you can redistribute it and/or |
7 | modify it under the terms of the GNU Library General Public |
8 | License as published by the Free Software Foundation; either |
9 | version 2 of the License, or (at your option) any later version. |
10 | |
11 | This library is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | Library General Public 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 |
18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
19 | Boston, MA 02110-1301, USA. |
20 | */ |
21 | /** |
22 | @file |
23 | This file is part of the API for handling calendar data and |
24 | defines the OccurrenceIterator class. |
25 | |
26 | @brief |
27 | This class provides an iterator to iterate over all occurrences of incidences. |
28 | |
29 | @author Christian Mollekopf \<mollekopf@kolabsys.com\> |
30 | */ |
31 | |
32 | #include "occurrenceiterator.h" |
33 | #include "calendar.h" |
34 | #include "calfilter.h" |
35 | |
36 | #include <KDebug> |
37 | #include <QDate> |
38 | |
39 | using namespace KCalCore; |
40 | |
41 | /** |
42 | Private class that helps to provide binary compatibility between releases. |
43 | @internal |
44 | */ |
45 | //@cond PRIVATE |
46 | class KCalCore::OccurrenceIterator::Private |
47 | { |
48 | public: |
49 | Private(OccurrenceIterator *qq) |
50 | : q(qq), |
51 | occurrenceIt(occurrenceList) |
52 | { |
53 | } |
54 | |
55 | OccurrenceIterator *q; |
56 | KDateTime start; |
57 | KDateTime end; |
58 | |
59 | struct Occurrence |
60 | { |
61 | Occurrence() |
62 | { |
63 | } |
64 | |
65 | Occurrence(const Incidence::Ptr &i, const KDateTime &d) |
66 | : incidence(i), date(d) |
67 | { |
68 | } |
69 | |
70 | Incidence::Ptr incidence; |
71 | KDateTime date; |
72 | }; |
73 | QList<Occurrence> occurrenceList; |
74 | QListIterator<Occurrence> occurrenceIt; |
75 | Occurrence current; |
76 | |
77 | /* |
78 | * KCalCore::CalFilter can't handle individual occurrences. |
79 | * When filtering completed to-dos, the CalFilter doesn't hide |
80 | * them if it's a recurring to-do. |
81 | */ |
82 | bool occurrenceIsHidden(const Calendar &calendar, |
83 | const Incidence::Ptr &inc, |
84 | const KDateTime &occurrenceDate) |
85 | { |
86 | if ((inc->type() == Incidence::TypeTodo) && |
87 | calendar.filter() && |
88 | (calendar.filter()->criteria() & KCalCore::CalFilter::HideCompletedTodos)) { |
89 | if (inc->recurs()) { |
90 | const Todo::Ptr todo = inc.staticCast<Todo>(); |
91 | if (todo && (occurrenceDate < todo->dtDue())) { |
92 | return true; |
93 | } |
94 | } else if (inc->hasRecurrenceId()) { |
95 | const Todo::Ptr mainTodo = calendar.todo(inc->uid()); |
96 | if (mainTodo && mainTodo->isCompleted()) { |
97 | return true; |
98 | } |
99 | } |
100 | } |
101 | return false; |
102 | } |
103 | |
104 | void setupIterator(const Calendar &calendar, const Incidence::List &incidences) |
105 | { |
106 | foreach(const Incidence::Ptr &inc, incidences) { |
107 | if (inc->hasRecurrenceId()) { |
108 | continue; |
109 | } |
110 | if (inc->recurs()) { |
111 | QHash<KDateTime, Incidence::Ptr> recurrenceIds; |
112 | KDateTime incidenceRecStart = inc->dateTime(Incidence::RoleRecurrenceStart); |
113 | foreach(const Incidence::Ptr &exception, calendar.instances(inc)) { |
114 | if (incidenceRecStart.isValid()) |
115 | recurrenceIds.insert(exception->recurrenceId().toTimeSpec(incidenceRecStart.timeSpec()), exception); |
116 | } |
117 | const bool isAllDay = inc->allDay(); |
118 | const DateTimeList occurrences = inc->recurrence()->timesInInterval(start, end); |
119 | Incidence::Ptr incidence(inc); |
120 | qint64 offset(0); |
121 | foreach(KDateTime occurrenceDate, occurrences) { //krazy:exclude=foreach |
122 | //timesInInterval generates always date-times, |
123 | //which is not what we want for all-day events |
124 | occurrenceDate.setDateOnly(isAllDay); |
125 | |
126 | bool resetIncidence = false; |
127 | if (recurrenceIds.contains(occurrenceDate)) { |
128 | // TODO: exclude exceptions where the start/end is not within |
129 | // (so the occurrence of the recurrence is omitted, but no exception is added) |
130 | if (recurrenceIds.value(occurrenceDate)->status() == Incidence::StatusCanceled) |
131 | continue; |
132 | |
133 | incidence = recurrenceIds.value(occurrenceDate); |
134 | occurrenceDate = incidence->dtStart(); |
135 | resetIncidence = !incidence->thisAndFuture(); |
136 | offset = incidence->recurrenceId().secsTo_long(incidence->dtStart()); |
137 | } else if (inc != incidence) { //thisAndFuture exception is active |
138 | occurrenceDate = occurrenceDate.addSecs(offset); |
139 | } |
140 | if (!occurrenceIsHidden(calendar, incidence, occurrenceDate)) { |
141 | occurrenceList << Private::Occurrence(incidence, occurrenceDate); |
142 | } |
143 | if (resetIncidence) { |
144 | incidence = inc; |
145 | offset = 0; |
146 | } |
147 | } |
148 | } else { |
149 | occurrenceList << Private::Occurrence(inc, inc->dtStart()); |
150 | } |
151 | } |
152 | occurrenceIt = QListIterator<Private::Occurrence>(occurrenceList); |
153 | } |
154 | }; |
155 | //@endcond |
156 | |
157 | static uint qHash(const KDateTime &dt) |
158 | { |
159 | return qHash(dt.toString()); |
160 | } |
161 | |
162 | /** |
163 | * Right now there is little point in the iterator, but: |
164 | * With an iterator it should be possible to solve this more memory efficiently |
165 | * and with immediate results at the beginning of the selected timeframe. |
166 | * Either all events are iterated simoulatneously, resulting in occurrences |
167 | * of all events in parallel in the correct time-order, or incidence after |
168 | * incidence, which would be even more efficient. |
169 | * |
170 | * By making this class a friend of calendar, we could also use the internally |
171 | * available data structures. |
172 | */ |
173 | OccurrenceIterator::OccurrenceIterator(const Calendar &calendar, |
174 | const KDateTime &start, |
175 | const KDateTime &end) |
176 | : d(new KCalCore::OccurrenceIterator::Private(this)) |
177 | { |
178 | d->start = start; |
179 | d->end = end; |
180 | |
181 | Event::List events = calendar.rawEvents(start.date(), end.date(), start.timeSpec()); |
182 | if (calendar.filter()) { |
183 | calendar.filter()->apply(&events); |
184 | } |
185 | |
186 | Todo::List todos = calendar.rawTodos(start.date(), end.date(), start.timeSpec()); |
187 | if (calendar.filter()) { |
188 | calendar.filter()->apply(&todos); |
189 | } |
190 | |
191 | Journal::List journals; |
192 | const Journal::List allJournals = calendar.rawJournals(); |
193 | foreach(const KCalCore::Journal::Ptr &journal, allJournals) { |
194 | const QDate journalStart = journal->dtStart().toTimeSpec(start.timeSpec()).date(); |
195 | if (journal->dtStart().isValid() && |
196 | journalStart >= start.date() && |
197 | journalStart <= end.date()) |
198 | journals << journal; |
199 | } |
200 | |
201 | if (calendar.filter()) { |
202 | calendar.filter()->apply(&journals); |
203 | } |
204 | |
205 | const Incidence::List incidences = |
206 | KCalCore::Calendar::mergeIncidenceList(events, todos, journals); |
207 | d->setupIterator(calendar, incidences); |
208 | } |
209 | |
210 | OccurrenceIterator::OccurrenceIterator(const Calendar &calendar, |
211 | const Incidence::Ptr &incidence, |
212 | const KDateTime &start, |
213 | const KDateTime &end) |
214 | : d(new KCalCore::OccurrenceIterator::Private(this)) |
215 | { |
216 | Q_ASSERT(incidence); |
217 | d->start = start; |
218 | d->end = end; |
219 | d->setupIterator(calendar, Incidence::List() << incidence); |
220 | } |
221 | |
222 | OccurrenceIterator::~OccurrenceIterator() |
223 | { |
224 | } |
225 | |
226 | bool OccurrenceIterator::hasNext() const |
227 | { |
228 | return d->occurrenceIt.hasNext(); |
229 | } |
230 | |
231 | void OccurrenceIterator::next() |
232 | { |
233 | d->current = d->occurrenceIt.next(); |
234 | } |
235 | |
236 | Incidence::Ptr OccurrenceIterator::incidence() const |
237 | { |
238 | return d->current.incidence; |
239 | } |
240 | |
241 | KDateTime OccurrenceIterator::occurrenceStartDate() const |
242 | { |
243 | return d->current.date; |
244 | } |
245 | |