1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
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 Event class.
25
26 @brief
27 This class provides an Event in the sense of RFC2445.
28
29 @author Cornelius Schumacher \<schumacher@kde.org\>
30*/
31
32#include "event.h"
33#include "visitor.h"
34
35#include <KDebug>
36
37#include <QDate>
38
39using namespace KCalCore;
40
41/**
42 Private class that helps to provide binary compatibility between releases.
43 @internal
44*/
45//@cond PRIVATE
46class KCalCore::Event::Private
47{
48public:
49 Private()
50 : mHasEndDate(false),
51 mTransparency(Opaque),
52 mMultiDayValid(false),
53 mMultiDay(false)
54 {}
55 Private(const KCalCore::Event::Private &other)
56 : mDtEnd(other.mDtEnd),
57 mHasEndDate(other.mHasEndDate),
58 mTransparency(other.mTransparency),
59 mMultiDayValid(false),
60 mMultiDay(false)
61 {}
62
63 KDateTime mDtEnd;
64 bool mHasEndDate;
65 Transparency mTransparency;
66 bool mMultiDayValid;
67 bool mMultiDay;
68};
69//@endcond
70
71Event::Event()
72 : d(new KCalCore::Event::Private)
73{
74}
75
76Event::Event(const Event &other)
77 : Incidence(other), d(new KCalCore::Event::Private(*other.d))
78{
79}
80
81Event::~Event()
82{
83 delete d;
84}
85
86Event *Event::clone() const
87{
88 return new Event(*this);
89}
90
91IncidenceBase &Event::assign(const IncidenceBase &other)
92{
93 if (&other != this) {
94 Incidence::assign(other);
95 const Event *e = static_cast<const Event*>(&other);
96 *d = *(e->d);
97 }
98 return *this;
99}
100
101bool Event::equals(const IncidenceBase &event) const
102{
103 if (!Incidence::equals(event)) {
104 return false;
105 } else {
106 // If they weren't the same type IncidenceBase::equals would had returned false already
107 const Event *e = static_cast<const Event*>(&event);
108 return
109 ((dtEnd() == e->dtEnd()) ||
110 (!dtEnd().isValid() && !e->dtEnd().isValid())) &&
111 hasEndDate() == e->hasEndDate() &&
112 transparency() == e->transparency();
113 }
114}
115
116Incidence::IncidenceType Event::type() const
117{
118 return TypeEvent;
119}
120
121QByteArray Event::typeStr() const
122{
123 return "Event";
124}
125
126void Event::setDtStart(const KDateTime &dt)
127{
128 d->mMultiDayValid = false;
129 Incidence::setDtStart(dt);
130}
131
132void Event::setDtEnd(const KDateTime &dtEnd)
133{
134 if (mReadOnly) {
135 return;
136 }
137
138 update();
139
140 d->mDtEnd = dtEnd;
141 d->mMultiDayValid = false;
142 d->mHasEndDate = dtEnd.isValid();
143 if (d->mHasEndDate) {
144 setHasDuration(false);
145 }
146 setFieldDirty(FieldDtEnd);
147 updated();
148}
149
150KDateTime Event::dtEnd() const
151{
152 if (hasEndDate()) {
153 return d->mDtEnd;
154 }
155
156 if (hasDuration()) {
157 if (allDay()) {
158 // For all day events, dtEnd is always inclusive
159 KDateTime end = duration().end(dtStart()).addDays(-1);
160 return end >= dtStart() ? end : dtStart();
161 } else {
162 return duration().end(dtStart());
163 }
164 }
165
166 // It is valid for a VEVENT to be without a DTEND. See RFC2445, Sect4.6.1.
167 // Be careful to use Event::dateEnd() as appropriate due to this possibility.
168 return dtStart();
169}
170
171QDate Event::dateEnd() const
172{
173 KDateTime end = dtEnd().toTimeSpec(dtStart());
174 if (allDay()) {
175 return end.date();
176 } else {
177 return end.addSecs(-1).date();
178 }
179}
180
181void Event::setHasEndDate(bool b)
182{
183 d->mHasEndDate = b;
184 setFieldDirty(FieldDtEnd);
185}
186
187bool Event::hasEndDate() const
188{
189 return d->mHasEndDate;
190}
191
192bool Event::isMultiDay(const KDateTime::Spec &spec) const
193{
194 // First off, if spec's not valid, we can check for cache
195 if (!spec.isValid() && d->mMultiDayValid) {
196 return d->mMultiDay;
197 }
198
199 // Not in cache -> do it the hard way
200 KDateTime start, end;
201
202 if (!spec.isValid()) {
203 start = dtStart();
204 end = dtEnd();
205 } else {
206 start = dtStart().toTimeSpec(spec);
207 end = dtEnd().toTimeSpec(spec);
208 }
209
210 // End date is non inclusive, so subtract 1 second... except if we
211 // got the event from some braindead implementation which gave us
212 // start == end one (those do happen)
213 if (start != end) {
214 end = end.addSecs(-1);
215 }
216
217 const bool multi = (start.date() != end.date() && start <= end);
218
219 // Update the cache
220 if (spec.isValid()) {
221 d->mMultiDayValid = true;
222 d->mMultiDay = multi;
223 }
224 return multi;
225}
226
227void Event::shiftTimes(const KDateTime::Spec &oldSpec,
228 const KDateTime::Spec &newSpec)
229{
230 Incidence::shiftTimes(oldSpec, newSpec);
231 if (hasEndDate()) {
232 d->mDtEnd = d->mDtEnd.toTimeSpec(oldSpec);
233 d->mDtEnd.setTimeSpec(newSpec);
234 }
235}
236
237void Event::setTransparency(Event::Transparency transparency)
238{
239 if (mReadOnly) {
240 return;
241 }
242 update();
243 d->mTransparency = transparency;
244 setFieldDirty(FieldTransparency);
245 updated();
246}
247
248Event::Transparency Event::transparency() const
249{
250 return d->mTransparency;
251}
252
253void Event::setDuration(const Duration &duration)
254{
255 setDtEnd(KDateTime());
256 Incidence::setDuration(duration);
257}
258
259void Event::setAllDay(bool allday)
260{
261 if (allday != allDay() && !mReadOnly) {
262 setFieldDirty(FieldDtEnd);
263 Incidence::setAllDay(allday);
264 }
265}
266
267bool Event::accept(Visitor &v, IncidenceBase::Ptr incidence)
268{
269 return v.visit(incidence.staticCast<Event>());
270}
271
272KDateTime Event::dateTime(DateTimeRole role) const
273{
274 switch (role) {
275 case RoleRecurrenceStart:
276 case RoleAlarmStartOffset:
277 case RoleStartTimeZone:
278 case RoleSort:
279 return dtStart();
280 case RoleCalendarHashing:
281 return !recurs() && !isMultiDay() ? dtStart() :
282 KDateTime();
283 case RoleAlarmEndOffset:
284 case RoleEndTimeZone:
285 case RoleEndRecurrenceBase:
286 case RoleEnd:
287 case RoleDisplayEnd:
288 return dtEnd();
289 case RoleDisplayStart:
290 return dtStart();
291 case RoleAlarm:
292 if (alarms().isEmpty()) {
293 return KDateTime();
294 } else {
295 Alarm::Ptr alarm = alarms().first();
296 return alarm->hasStartOffset() ? dtStart() : dtEnd();
297 }
298 break;
299 default:
300 return KDateTime();
301 }
302}
303
304void Event::setDateTime(const KDateTime &dateTime, DateTimeRole role)
305{
306 switch (role) {
307 case RoleDnD:
308 {
309 const int duration = dtStart().secsTo(dtEnd());
310
311 setDtStart(dateTime);
312 setDtEnd(dateTime.addSecs(duration <= 0 ? 3600 : duration));
313 break;
314 }
315 case RoleEnd:
316 setDtEnd(dateTime);
317 break;
318 default:
319 kDebug() << "Unhandled role" << role;
320 }
321}
322
323void Event::virtual_hook(int id, void *data)
324{
325 switch (static_cast<IncidenceBase::VirtualHook>(id)) {
326 case IncidenceBase::SerializerHook:
327 serialize(*reinterpret_cast<QDataStream*>(data));
328 break;
329 case IncidenceBase::DeserializerHook:
330 deserialize(*reinterpret_cast<QDataStream*>(data));
331 break;
332 default:
333 Q_ASSERT(false);
334 }
335}
336
337QLatin1String KCalCore::Event::mimeType() const
338{
339 return Event::eventMimeType();
340}
341
342QLatin1String Event::eventMimeType()
343{
344 return QLatin1String("application/x-vnd.akonadi.calendar.event");
345}
346
347QLatin1String Event::iconName(const KDateTime &) const
348{
349 return QLatin1String("view-calendar-day");
350}
351
352void Event::serialize(QDataStream &out)
353{
354 Incidence::serialize(out);
355 out << d->mDtEnd << d->mHasEndDate << static_cast<quint32>(d->mTransparency) << d->mMultiDayValid << d->mMultiDay;
356}
357
358void Event::deserialize(QDataStream &in)
359{
360 Incidence::deserialize(in);
361 in >> d->mDtEnd >> d->mHasEndDate;
362 quint32 transp;
363 in >> transp;
364 d->mTransparency = static_cast<Transparency>(transp);
365 in >> d->mMultiDayValid >> d->mMultiDay;
366}
367