2
Copyright (c) 2009 KDAB
3
Author: Sebastian Sauer <sebsauer@kdab.net>
4
Frank Osterfeld <frank@kdab.net>
6
This program is free software; you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation; either version 2 of the License, or
9
(at your option) any later version.
11
This program 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
14
GNU General Public License for more details.
16
You should have received a copy of the GNU General Public License along
17
with this program; if not, write to the Free Software Foundation, Inc.,
18
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
#include "calendar_p.h"
24
#include "collectionselection.h"
25
#include "blockalarmsattribute.h"
29
#include <KSelectionProxyModel>
30
#include <Akonadi/EntityMimeTypeFilterModel>
31
#include <QtCore/QMultiHash>
32
#include <QItemSelection>
34
using namespace CalendarSupport;
36
Calendar::Private::Private( QAbstractItemModel *treeModel, QAbstractItemModel *model, Calendar *qq )
38
mTimeZones( new KCalCore::ICalTimeZones ),
39
mNewObserver( false ),
40
mObserversEnabled( true ),
41
mDefaultFilter( new KCalCore::CalFilter ),
42
m_treeModel( treeModel ),
45
// Setup default filter, which does nothing
46
mDefaultFilter->setEnabled( false );
47
m_filterProxy = new CalFilterProxyModel( q );
48
m_filterProxy->setFilter( mDefaultFilter );
49
m_filterProxy->setSourceModel( model );
50
m_filterProxy->setObjectName( "Implements KCalCore filtering functionality" );
52
// user information...
53
mOwner.setName( i18n( "Unknown Name" ) );
54
mOwner.setEmail( i18n( "unknown@nowhere" ) );
56
connect( m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
57
this, SLOT(dataChanged(QModelIndex,QModelIndex)) );
59
connect( m_model, SIGNAL(layoutChanged()),
60
this, SLOT(layoutChanged()) );
62
connect( m_model, SIGNAL(modelReset()),
63
this, SLOT(modelReset()) );
65
connect( m_model, SIGNAL(rowsInserted(QModelIndex,int,int)),
66
this, SLOT(rowsInserted(QModelIndex,int,int)) );
68
connect( m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
69
this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)) );
71
// use the unfiltered model to catch collections
72
connect( m_treeModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
73
this, SLOT(rowsInsertedInTreeModel(QModelIndex,int,int)) );
75
connect( m_treeModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
76
this, SLOT(rowsAboutToBeRemovedInTreeModel(QModelIndex,int,int)) );
78
connect( m_treeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
79
this, SLOT(dataChangedInTreeModel(QModelIndex,QModelIndex)) );
81
connect( m_treeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
82
SLOT(onRowsMovedInTreeModel(QModelIndex,int,int,QModelIndex,int)) );
85
connect( m_monitor, SIGNAL(itemLinked(const Akonadi::Item,Akonadi::Collection)),
86
this, SLOT(itemAdded(const Akonadi::Item,Akonadi::Collection)) );
87
connect( m_monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection )),
88
this, SLOT(itemRemoved(Akonadi::Item,Akonadi::Collection )) );
92
void Calendar::Private::rowsInsertedInTreeModel( const QModelIndex &parent, int start, int end )
94
collectionsAdded( collectionsFromModel( m_treeModel, parent, start, end ) );
97
void Calendar::Private::rowsAboutToBeRemovedInTreeModel( const QModelIndex &parent,
100
collectionsRemoved( collectionsFromModel( m_treeModel, parent, start, end ) );
103
void Calendar::Private::dataChangedInTreeModel( const QModelIndex &topLeft,
104
const QModelIndex &bottomRight )
106
Q_ASSERT( topLeft.row() <= bottomRight.row() );
107
const int endRow = bottomRight.row();
108
QModelIndex i( topLeft );
110
while ( row <= endRow ) {
111
const Akonadi::Collection col = collectionFromIndex( i );
112
if ( col.isValid() ) {
113
// Attributes might have changed, store the new collection and discard the old one
114
m_collectionMap.insert( col.id(), col );
117
i = i.sibling( row, topLeft.column() );
121
void Calendar::Private::rowsInserted( const QModelIndex &parent, int start, int end )
123
itemsAdded( itemsFromModel( m_model, parent, start, end ) );
126
void Calendar::Private::rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end )
128
itemsRemoved( itemsFromModel( m_model, parent, start, end ) );
131
void Calendar::Private::layoutChanged()
136
void Calendar::Private::onRowsMovedInTreeModel( const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
137
const QModelIndex &destinationParent, int destinationRow )
139
Q_ASSERT( sourceEnd >= sourceStart );
140
Q_ASSERT( sourceStart >= 0 );
141
Q_ASSERT( destinationRow >= 0 );
143
const Akonadi::Collection sourceCollection = collectionFromIndex( sourceParent );
144
const Akonadi::Collection destinationCollection = collectionFromIndex( destinationParent );
146
if ( sourceCollection.isValid() && destinationCollection.isValid() &&
147
sourceCollection.id() != destinationCollection.id() ) {
148
const int numItems = sourceEnd - sourceStart + 1;
149
Akonadi::Item::List movedItems = itemsFromModel( m_treeModel, destinationParent, destinationRow,
150
destinationRow + numItems - 1 );
153
// KSelectionProxyModel doesn't honour rowsMoved() yet, so, if the source model emitted rowsMoved
154
// (items changing collection) we could only catch it in the onLayoutChanged() slot, which isn't
155
// performant. So we listen to the source model's rowsMoved() and check manuall if it when in or
156
// out of the selection, and notify the application.
157
Akonadi::EntityMimeTypeFilterModel *m = qobject_cast<Akonadi::EntityMimeTypeFilterModel*>( m_model );
159
KSelectionProxyModel *sm = qobject_cast<KSelectionProxyModel*>( m->sourceModel() );
161
CollectionSelection collectionSelection( sm->selectionModel() );
162
const bool sourceCollectionIsSelected = collectionSelection.contains( sourceCollection.id() );
163
const bool destinationCollectionIsSelected = collectionSelection.contains( destinationCollection.id() );
164
if ( sourceCollectionIsSelected && destinationCollectionIsSelected ) {
165
foreach( const Akonadi::Item item, movedItems ) {
166
if ( item.isValid() && item.hasPayload<KCalCore::Incidence::Ptr>() ) {
167
// We have old items ( that think they belong to another collection ) inside m_itemMap
168
if ( m_itemMap.contains( item.id() ) ) {
169
itemsRemoved( movedItems );
170
itemsAdded( movedItems );
174
} else if ( !sourceCollectionIsSelected && destinationCollectionIsSelected ) { // Added
175
itemsAdded( movedItems );
176
} else if ( sourceCollectionIsSelected && !destinationCollectionIsSelected ) { // Removed
177
itemsRemoved( movedItems );
185
void Calendar::Private::appendVirtualItems( Akonadi::Item::List &itemList )
187
foreach( const Akonadi::Item &item, itemList ) {
188
if ( m_virtualItems.contains( item.id() ) ) {
189
itemList.append( m_virtualItems.value( item.id() ) );
194
void Calendar::Private::modelReset()
200
void Calendar::Private::clear()
202
itemsRemoved( m_itemMap.values() );
203
Q_ASSERT( m_itemMap.isEmpty() );
204
m_childToParent.clear();
205
m_parentToChildren.clear();
206
m_childToUnseenParent.clear();
207
m_unseenParentToChildren.clear();
208
m_itemIdsForDate.clear();
209
m_itemDateForItemId.clear();
210
m_virtualItems.clear();
213
void Calendar::Private::readFromModel()
215
itemsAdded( itemsFromModel( m_model ) );
218
void Calendar::Private::dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
221
Q_ASSERT( topLeft.row() <= bottomRight.row() );
222
const int endRow = bottomRight.row();
223
QModelIndex i( topLeft );
225
while ( row <= endRow ) {
226
const Akonadi::Item item = itemFromIndex( i );
227
if ( item.isValid() ) {
228
updateItem( item, AssertExists );
231
i = i.sibling( row, topLeft.column() );
233
emit q->calendarChanged();
236
Calendar::Private::~Private()
238
Q_FOREACH ( const Akonadi::Item &item, m_itemMap ) {
239
CalendarSupport::incidence( item )->unRegisterObserver( q );
243
delete mDefaultFilter;
246
void Calendar::Private::assertInvariants() const
250
void Calendar::Private::updateItem( const Akonadi::Item &item, UpdateMode mode )
253
const bool alreadyExisted = m_itemMap.contains( item.id() );
254
const Akonadi::Item::Id id = item.id();
256
const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
262
// TODO: remove this debug message in a few months
263
kDebug() << "id=" << item.id()
264
<< "version=" << item.revision()
265
<< "alreadyExisted=" << alreadyExisted
266
<< "; mode = " << mode
267
<< "; uid = " << incidence->uid()
268
<< "; storageCollection.id() = " << item.storageCollectionId() // the real collection
269
<< "; parentCollection.id() = " << item.parentCollection().id(); // can be a virtual collection
271
if ( mode != AssertExists && alreadyExisted ) {
272
// An item from a virtual folder was inserted and we already have an item with
273
// this id, belonging to the real collection. So we just insert it in m_virtualItems
274
// so we keep track of it. Most hashes are indexed by Item::Id, and korg does lookups by Id too,
275
// so we can't just treat this item as an independent one.
276
if ( m_itemMap[id].parentCollection().id() != item.parentCollection().id() ) {
277
m_virtualItems[item.id()].append( item );
278
q->notifyIncidenceAdded( item );
280
kError() << "Item " << item.id() << " is already known.";
285
//Q_ASSERT( mode == DontCare || alreadyExisted == ( mode == AssertExists ) );
287
if ( alreadyExisted ) {
288
if ( !m_itemMap.contains( id ) ) {
289
// Item was deleted almost at the same time the change was made
290
// ignore this change
294
if ( item.storageCollectionId() == -1 ) {
295
// A valid item can have an invalid storage id if it was deleted while
296
// fetching the ancestor
300
if ( item.storageCollectionId() != m_itemMap.value( id ).storageCollectionId() ) {
301
// An item moved happened, update our internal copy, the storateCollectionId has changed.
302
Akonadi::Collection::Id oldCollectionId = m_itemMap.value( id ).storageCollectionId();
303
m_itemMap.insert( id, item );
304
if ( item.isValid() ) {
307
oldUi.collection = oldCollectionId;
308
oldUi.uid = incidence->uid();
309
if ( m_uidToItemId.contains( oldUi ) ) {
310
newUi.collection = item.storageCollectionId();
311
newUi.uid = oldUi.uid;
312
m_uidToItemId.remove( oldUi );
313
m_uidToItemId.insert( newUi, item.id() );
315
Q_ASSERT_X( false, "Calendar::Private::updateItem", "Item wasn't found in m_uidToItemId" );
320
// update-only goes here
322
// new-only goes here
323
const Akonadi::Collection::Rights rights = item.parentCollection().rights();
324
if ( !( rights & Akonadi::Collection::CanDeleteItem ) &&
325
!( rights & Akonadi::Collection::CanChangeItem ) &&
326
!incidence->isReadOnly() ) {
327
incidence->setReadOnly( true );
331
if ( alreadyExisted && m_itemDateForItemId.contains( item.id() ) ) {
332
// for changed items, we must remove existing date entries (they might have changed)
333
m_itemIdsForDate.remove( m_itemDateForItemId[item.id()], item.id() );
334
m_itemDateForItemId.remove( item.id() );
338
if ( const KCalCore::Todo::Ptr t = CalendarSupport::todo( item ) ) {
339
if ( t->hasDueDate() ) {
340
date = t->dtDue().date().toString();
342
} else if ( const KCalCore::Event::Ptr e = CalendarSupport::event( item ) ) {
343
if ( !e->recurs() && !e->isMultiDay() ) {
344
date = e->dtStart().date().toString();
346
} else if ( const KCalCore::Journal::Ptr j = CalendarSupport::journal( item ) ) {
347
date = j->dtStart().date().toString();
349
kError() << "Item id is " << item.id()
350
<< item.hasPayload<KCalCore::Incidence::Ptr>()
351
<< item.hasPayload<KCalCore::Event::Ptr>()
352
<< item.hasPayload<KCalCore::Todo::Ptr>()
353
<< item.hasPayload<KCalCore::Journal::Ptr>();
354
KCalCore::Incidence::Ptr p = CalendarSupport::incidence( item );
356
kError() << "incidence uid is " << p->uid()
357
<< " and type is " << p->typeStr();
364
if ( !m_itemIdsForDate.contains( date, item.id() ) && !date.isEmpty() ) {
365
m_itemIdsForDate.insert( date, item.id() );
366
m_itemDateForItemId.insert( item.id(), date );
369
m_itemMap.insert( id, item );
372
ui.collection = item.storageCollectionId();
373
ui.uid = incidence->uid();
375
//REVIEW(AKONADI_PORT)
376
//UIDs might be duplicated and thus not unique, so for now we assume that the relatedTo
377
// UID refers to an item in the same collection.
378
//this might break with virtual collections, so we might fall back to a global UID
379
//to akonadi item mapping, and pick just any item (or the first found, or whatever
380
//strategy makes sense) from the ones with the same UID
381
const QString parentUID = incidence->relatedTo();
382
const bool hasParent = !parentUID.isEmpty();
383
UnseenItem parentItem;
384
QMap<UnseenItem,Akonadi::Item::Id>::const_iterator parentIt = m_uidToItemId.constEnd();
385
bool knowParent = false;
386
bool parentNotChanged = false;
388
parentItem.collection = item.storageCollectionId();
389
parentItem.uid = parentUID;
390
parentIt = m_uidToItemId.constFind( parentItem );
391
knowParent = parentIt != m_uidToItemId.constEnd();
394
if ( alreadyExisted ) { // We're updating an existing item
395
const bool existedInUidMap = m_uidToItemId.contains( ui );
396
if ( m_uidToItemId.value( ui ) != item.id() ) {
397
kError()<< "Ignoring item. item.id() = " << item.id() << "; cached id = " << m_uidToItemId.value( ui )
398
<< "; item uid = " << ui.uid
399
<< "; calendar = " << q->objectName()
400
<< "; existed in cache = " << existedInUidMap
401
<< "; storageCollection.id() = " << item.storageCollectionId() // the real collection
402
<< "; parentCollection.id() = " << item.parentCollection().id() // can be a virtual collection
403
<< "; hasParent = " << hasParent
404
<< "; knowParent = " << knowParent;
405
if ( existedInUidMap ) {
406
Q_ASSERT_X( false, "updateItem", "uidToId map disagrees with item id" );
408
kDebug() << "m_uidToItemId has size " << m_uidToItemId.count();
409
QMapIterator<UnseenItem, Akonadi::Item::Id> i( m_uidToItemId );
410
while ( i.hasNext() ) {
412
if ( i.key().uid == ui.uid || i.value() == item.id() ) {
413
kDebug() << " key " << i.key().uid << i.key().collection << " has value " << i.value();
416
kError() << "Possible cause is that the resource isn't explicitly setting an uid ( and a random one is generated )";
417
Q_ASSERT_X( false, "updateItem", "Item not found inside m_uidToItemId" );
422
QHash<Akonadi::Item::Id,Akonadi::Item::Id>::Iterator oldParentIt = m_childToParent.find( id );
423
if ( oldParentIt != m_childToParent.end() ) {
424
const KCalCore::Incidence::Ptr parentInc =
425
CalendarSupport::incidence( m_itemMap.value( oldParentIt.value() ) );
426
Q_ASSERT( parentInc );
427
if ( parentInc->uid() != parentUID ) {
428
//parent changed, remove old entries
429
QList<Akonadi::Item::Id>& l = m_parentToChildren[oldParentIt.value()];
431
m_childToParent.remove( id );
433
parentNotChanged = true;
435
} else { //old parent not seen, maybe unseen?
436
QHash<Akonadi::Item::Id,UnseenItem>::Iterator oldUnseenParentIt =
437
m_childToUnseenParent.find( id );
438
if ( oldUnseenParentIt != m_childToUnseenParent.end() ) {
439
if ( oldUnseenParentIt.value().uid != parentUID ) {
440
//parent changed, remove old entries
441
QList<Akonadi::Item::Id>& l = m_unseenParentToChildren[oldUnseenParentIt.value()];
443
m_childToUnseenParent.remove( id );
445
parentNotChanged = true;
449
} else { // We're inserting a new item
450
m_uidToItemId.insert( ui, item.id() );
452
//check for already known children:
453
const QList<Akonadi::Item::Id> orphanedChildren = m_unseenParentToChildren.value( ui );
454
if ( !orphanedChildren.isEmpty() ) {
455
m_parentToChildren.insert( id, orphanedChildren );
458
Q_FOREACH ( const Akonadi::Item::Id &cid, orphanedChildren ) {
459
m_childToParent.insert( cid, id );
462
m_unseenParentToChildren.remove( ui );
463
m_childToUnseenParent.remove( id );
466
if ( hasParent && !parentNotChanged ) {
468
Q_ASSERT( !m_parentToChildren.value( parentIt.value() ).contains( id ) );
469
const KCalCore::Incidence::Ptr parentInc =
470
CalendarSupport::incidence( m_itemMap.value( parentIt.value() ) );
471
Q_ASSERT( parentInc );
472
m_parentToChildren[parentIt.value()].append( id );
473
m_childToParent.insert( id, parentIt.value() );
475
m_childToUnseenParent.insert( id, parentItem );
476
m_unseenParentToChildren[parentItem].append( id );
480
if ( !alreadyExisted ) {
481
incidence->registerObserver( q );
482
q->notifyIncidenceAdded( item );
484
q->notifyIncidenceChanged( item );
489
void Calendar::Private::itemChanged( const Akonadi::Item &item )
492
Q_ASSERT( item.isValid() );
493
const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
495
kWarning() << "Really? No incidence for item.id() " << item.id();
498
updateItem( item, AssertExists );
499
emit q->calendarChanged();
503
void Calendar::Private::itemsAdded( const Akonadi::Item::List &items )
506
foreach ( const Akonadi::Item &item, items ) {
507
Q_ASSERT( item.isValid() );
508
if ( !hasIncidence( item ) ) {
511
updateItem( item, AssertNew );
512
const KCalCore::Incidence::Ptr incidence = item.payload<KCalCore::Incidence::Ptr>();
514
emit q->calendarChanged();
518
void Calendar::Private::collectionsAdded( const Akonadi::Collection::List &collections )
520
foreach ( const Akonadi::Collection &collection, collections ) {
521
m_collectionMap[collection.id()] = collection;
525
void Calendar::Private::collectionsRemoved( const Akonadi::Collection::List &collections )
527
// kDebug() << "removing collections: " << collections.count();
528
foreach ( const Akonadi::Collection &collection, collections ) {
529
m_collectionMap.remove( collection.id() );
533
void Calendar::Private::removeItemFromMaps( const Akonadi::Item &item )
535
UnseenItem unseen_item;
536
UnseenItem unseen_parent;
538
unseen_item.collection = unseen_parent.collection = item.storageCollectionId();
540
KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
542
unseen_item.uid = incidence->uid();
543
unseen_parent.uid = incidence->relatedTo();
546
if ( m_childToParent.contains( item.id() ) ) {
547
Akonadi::Item::Id parentId = m_childToParent.take( item.id() );
548
m_parentToChildren[parentId].removeAll( item.id() );
551
foreach ( const Akonadi::Item::Id &id, m_parentToChildren[item.id()] ) {
552
m_childToUnseenParent[id] = unseen_item;
553
m_unseenParentToChildren[unseen_item].push_back( id );
556
m_parentToChildren.remove( item.id() );
558
m_childToUnseenParent.remove( item.id() );
560
m_unseenParentToChildren[unseen_parent].removeAll( item.id() );
562
m_uidToItemId.remove( unseen_item );
563
m_itemDateForItemId.remove( item.id() );
565
const QList<QString> entriesToDelete = m_itemIdsForDate.keys( item.id() );
566
foreach( const QString &entryToDelete, entriesToDelete ) {
567
m_itemIdsForDate.remove( entryToDelete );
571
void Calendar::Private::itemsRemoved( const Akonadi::Item::List &items )
574
foreach ( const Akonadi::Item &item, items ) {
575
Q_ASSERT( item.isValid() );
577
if ( !m_virtualItems.value( item.id() ).isEmpty() ) {
578
// We have more than one item with the same id, due to virtual folders, so we can't
579
// cleanup any hashes, to-do hierarchies, id to uid maps, etc, yet. We can only do that
580
// when the last item is removed. Just decrement and return.
581
m_virtualItems[item.id()].removeLast();
582
q->notifyIncidenceDeleted( item );
583
emit q->calendarChanged();
587
Akonadi::Item oldItem( m_itemMap.take( item.id() ) );
589
removeItemFromMaps( oldItem );
591
Q_ASSERT( oldItem.hasPayload<KCalCore::Incidence::Ptr>() );
592
const KCalCore::Incidence::Ptr incidence = oldItem.payload<KCalCore::Incidence::Ptr>();
594
kDebug() << "Remove uid=" << incidence->uid()
595
<< "summary=" << incidence->summary()
596
<< "type=" << int( incidence->type() )
597
<< "; id= " << item.id() << "; revision=" << item.revision()
602
if ( const KCalCore::Event::Ptr e = incidence.dynamicCast<KCalCore::Event>() ) {
603
if ( !e->recurs() ) {
604
m_itemIdsForDate.remove( e->dtStart().date().toString(), item.id() );
606
} else if ( const KCalCore::Todo::Ptr t = incidence.dynamicCast<KCalCore::Todo>( ) ) {
607
if ( t->hasDueDate() ) {
608
m_itemIdsForDate.remove( t->dtDue().date().toString(), item.id() );
610
} else if ( const KCalCore::Journal::Ptr j = incidence.dynamicCast<KCalCore::Journal>() ) {
611
m_itemIdsForDate.remove( j->dtStart().date().toString(), item.id() );
613
kError() << "Unsupported incidence type: " << incidence;
618
// oldItem will almost always be the same as item, but, when you move an item from one collection
619
// and the destination collection isn't selected, itemsRemoved() is called, and they will differ
620
// on the parentCollection id.
621
q->notifyIncidenceDeleted( oldItem );
622
incidence->unRegisterObserver( q );
624
emit q->calendarChanged();
628
Calendar::Calendar( QAbstractItemModel *treeModel, QAbstractItemModel *model,
629
const KDateTime::Spec &timeSpec, QObject *parent )
630
: QObject( parent ), d( new Private( treeModel, model, this ) )
632
d->mTimeSpec = timeSpec;
633
d->mViewTimeSpec = timeSpec;
637
Calendar::~Calendar()
642
QAbstractItemModel *Calendar::treeModel() const
644
return d->m_treeModel;
647
QAbstractItemModel *Calendar::model() const
649
return d->m_filterProxy;
652
QAbstractItemModel *Calendar::unfilteredModel() const
657
void Calendar::setUnfilteredModel( QAbstractItemModel *model )
660
if ( d->m_model == model ) {
665
disconnect( d->m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
666
d, SLOT(dataChanged(QModelIndex,QModelIndex)) );
668
disconnect( d->m_model, SIGNAL(layoutChanged()),
669
d, SLOT(layoutChanged()) );
671
disconnect( d->m_model, SIGNAL(modelReset()),
672
d, SLOT(modelReset()) );
674
disconnect( d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)),
675
d, SLOT(rowsInserted(QModelIndex,int,int)) );
677
disconnect( d->m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
678
d, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)) );
681
d->m_filterProxy->setSourceModel( model );
683
connect( d->m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
684
d, SLOT(dataChanged(QModelIndex,QModelIndex)) );
686
connect( d->m_model, SIGNAL(layoutChanged()),
687
d, SLOT(layoutChanged()) );
689
connect( d->m_model, SIGNAL(modelReset()),
690
d, SLOT(modelReset()) );
692
connect( d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)),
693
d, SLOT(rowsInserted(QModelIndex,int,int)) );
695
connect( d->m_model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
696
d, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)) );
701
// This method will be called probably multiple times if a series of changes where done.
702
// One finished the endChange() method got called.
704
void Calendar::incidenceUpdate( const QString &uid, const KDateTime &recurrenceId )
707
Q_UNUSED( recurrenceId );
710
void Calendar::incidenceUpdated( const QString &uid, const KDateTime &recurrenceId )
712
Q_UNUSED( recurrenceId );
713
KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( itemForIncidenceUid( uid ) );
719
incidence->setLastModified( KDateTime::currentUtcDateTime() );
720
// we should probably update the revision number here,
721
// or internally in the Event itself when certain things change.
722
// need to verify with ical documentation.
724
// The static_cast is ok as the CalendarLocal only observes Incidence objects
725
#ifdef AKONADI_PORT_DISABLED
726
notifyIncidenceChanged( static_cast<KCalCore::Incidence::Ptr >( incidence ) );
728
kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO;
732
Akonadi::Item Calendar::event( Akonadi::Item::Id id ) const
734
const Akonadi::Item item = d->m_itemMap.value( id );
735
if ( CalendarSupport::event( item ) ) {
738
return Akonadi::Item();
742
Akonadi::Item Calendar::todo( Akonadi::Item::Id id ) const
744
const Akonadi::Item item = d->m_itemMap.value( id );
745
if ( CalendarSupport::todo( item ) ) {
748
return Akonadi::Item();
752
Akonadi::Item::List Calendar::rawTodos( TodoSortField sortField,
753
SortDirection sortDirection )
755
Akonadi::Item::List todoList;
756
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
757
while ( i.hasNext() ) {
759
if ( CalendarSupport::todo( i.value() ) ) {
760
todoList.append( i.value() );
763
d->appendVirtualItems( todoList );
764
return sortTodos( todoList, sortField, sortDirection );
767
Akonadi::Item::List Calendar::rawTodosForDate( const QDate &date )
769
Akonadi::Item::List todoList;
770
QString dateStr = date.toString();
771
QMultiHash<QString, Akonadi::Item::Id>::const_iterator it =
772
d->m_itemIdsForDate.constFind( dateStr );
773
while ( it != d->m_itemIdsForDate.constEnd() && it.key() == dateStr ) {
774
if ( CalendarSupport::todo( d->m_itemMap[it.value()] ) ) {
775
todoList.append( d->m_itemMap[it.value()] );
779
d->appendVirtualItems( todoList );
783
KCalCore::Alarm::List Calendar::alarmsTo( const KDateTime &to )
785
return alarms( KDateTime( QDate( 1900, 1, 1 ) ), to );
788
KCalCore::Alarm::List Calendar::alarms( const KDateTime &from, const KDateTime &to, bool excludeBlockedAlarms )
790
KCalCore::Alarm::List alarmList;
791
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
792
while ( i.hasNext() ) {
793
const Akonadi::Item item = i.next().value();
795
if ( excludeBlockedAlarms ) {
796
// take the collection from m_collectionMap, because we need the up-to-date collection attributes
797
const Akonadi::Collection parentCollection = d->m_collectionMap.value( item.storageCollectionId() );
798
if ( parentCollection.isValid() ) {
799
if ( parentCollection.hasAttribute<BlockAlarmsAttribute>() )
800
continue; // do not include alarms from this collection
804
KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
809
if ( incidence->recurs() ) {
810
appendRecurringAlarms( alarmList, item, from, to );
812
appendAlarms( alarmList, item, from, to );
818
Akonadi::Item::List Calendar::rawEventsForDate( const QDate &date,
819
const KDateTime::Spec ×pec,
820
EventSortField sortField,
821
SortDirection sortDirection )
823
Akonadi::Item::List eventList;
824
// Find the hash for the specified date
825
const QString dateStr = date.toString();
826
// Iterate over all non-recurring, single-day events that start on this date
827
QMultiHash<QString, Akonadi::Item::Id>::const_iterator it =
828
d->m_itemIdsForDate.constFind( dateStr );
829
KDateTime::Spec ts = timespec.isValid() ? timespec : timeSpec();
830
KDateTime kdt( date, ts );
831
while ( it != d->m_itemIdsForDate.constEnd() && it.key() == dateStr ) {
832
if ( KCalCore::Event::Ptr ev = CalendarSupport::event( d->m_itemMap[it.value()] ) ) {
833
KDateTime end( ev->dtEnd().toTimeSpec( ev->dtStart() ) );
834
if ( ev->allDay() ) {
835
end.setDateOnly( true );
837
end = end.addSecs( -1 );
840
eventList.append( d->m_itemMap[it.value()] );
845
// Iterate over all events. Look for recurring events that occur on this date
846
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
847
while ( i.hasNext() ) {
849
if ( KCalCore::Event::Ptr ev = CalendarSupport::event( i.value() ) ) {
850
if ( ev->recurs() ) {
851
if ( ev->isMultiDay() ) {
852
int extraDays = ev->dtStart().date().daysTo( ev->dtEnd().date() );
853
for ( int j = 0; j <= extraDays; ++j ) {
854
if ( ev->recursOn( date.addDays( -j ), ts ) ) {
855
eventList.append( i.value() );
860
if ( ev->recursOn( date, ts ) ) {
861
eventList.append( i.value() );
865
if ( ev->isMultiDay() ) {
866
if ( ev->dtStart().date() <= date && ev->dtEnd().date() >= date ) {
867
eventList.append( i.value() );
874
d->appendVirtualItems( eventList );
876
return sortEvents( eventList, sortField, sortDirection );
879
Akonadi::Item::List Calendar::rawEvents( const QDate &start, const QDate &end,
880
const KDateTime::Spec ×pec, bool inclusive )
882
Akonadi::Item::List eventList;
883
KDateTime::Spec ts = timespec.isValid() ? timespec : timeSpec();
884
KDateTime st( start, ts );
885
KDateTime nd( end, ts );
886
KDateTime yesterStart = st.addDays( -1 );
887
// Get non-recurring events
888
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
889
while ( i.hasNext() ) {
891
if ( KCalCore::Event::Ptr event = CalendarSupport::event( i.value() ) ) {
892
KDateTime rStart = event->dtStart();
893
if ( nd < rStart ) continue;
894
if ( inclusive && rStart < st ) {
897
if ( !event->recurs() ) { // non-recurring events
898
KDateTime rEnd = event->dtEnd();
902
if ( inclusive && nd < rEnd ) {
905
} else { // recurring events
906
switch( event->recurrence()->duration() ) {
912
case 0: // end date given
913
default: // count given
914
KDateTime rEnd( event->recurrence()->endDate(), ts );
915
if ( !rEnd.isValid() ) {
921
if ( inclusive && nd < rEnd ) {
925
} // switch(duration)
927
eventList.append( i.value() );
931
d->appendVirtualItems( eventList );
936
Akonadi::Item::List Calendar::rawEventsForDate( const KDateTime &kdt )
938
return rawEventsForDate( kdt.date(), kdt.timeSpec() );
941
Akonadi::Item::List Calendar::rawEvents( EventSortField sortField,
942
SortDirection sortDirection )
944
Akonadi::Item::List eventList;
945
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
946
while ( i.hasNext() ) {
948
if ( CalendarSupport::event( i.value() ) ) {
949
eventList.append( i.value() );
952
d->appendVirtualItems( eventList );
953
return sortEvents( eventList, sortField, sortDirection );
956
Akonadi::Item Calendar::journal( Akonadi::Item::Id id ) const
958
const Akonadi::Item item = d->m_itemMap.value( id );
959
if ( CalendarSupport::journal( item ) ) {
962
return Akonadi::Item();
966
Akonadi::Item::List Calendar::rawJournals( JournalSortField sortField,
967
SortDirection sortDirection )
969
Akonadi::Item::List journalList;
970
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
971
while ( i.hasNext() ) {
973
if ( CalendarSupport::journal( i.value() ) ) {
974
journalList.append( i.value() );
977
d->appendVirtualItems( journalList );
978
return sortJournals( journalList, sortField, sortDirection );
981
Akonadi::Item::List Calendar::rawJournalsForDate( const QDate &date )
983
Akonadi::Item::List journalList;
984
QString dateStr = date.toString();
985
QMultiHash<QString, Akonadi::Item::Id>::const_iterator it =
986
d->m_itemIdsForDate.constFind( dateStr );
987
while ( it != d->m_itemIdsForDate.constEnd() && it.key() == dateStr ) {
988
if ( CalendarSupport::journal( d->m_itemMap[it.value()] ) ) {
989
journalList.append( d->m_itemMap[it.value()] );
993
d->appendVirtualItems( journalList );
997
Akonadi::Item Calendar::findParent( const Akonadi::Item &child ) const
999
return d->m_itemMap.value( d->m_childToParent.value( child.id() ) );
1002
Akonadi::Item::List Calendar::findChildren( const KCalCore::Incidence::Ptr &incidence ) const
1004
Akonadi::Item item = itemForIncidenceUid( incidence->uid() );
1006
return findChildren( item );
1009
Akonadi::Item::List Calendar::findChildren( const Akonadi::Item &parent ) const
1011
Akonadi::Item::List l;
1012
Q_FOREACH( const Akonadi::Item::Id &id, d->m_parentToChildren.value( parent.id() ) ) {
1013
l.push_back( d->m_itemMap.value( id ) );
1018
bool Calendar::isChild( const Akonadi::Item &parent, const Akonadi::Item &child ) const
1020
return d->m_childToParent.value( child.id() ) == parent.id();
1023
Akonadi::Item::Id Calendar::itemIdForIncidenceUid( const QString &uid ) const
1025
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
1026
while ( i.hasNext() ) {
1028
const Akonadi::Item item = i.value();
1029
Q_ASSERT( item.isValid() );
1030
Q_ASSERT( item.hasPayload<KCalCore::Incidence::Ptr>() );
1031
KCalCore::Incidence::Ptr inc = item.payload<KCalCore::Incidence::Ptr>();
1032
if ( inc->uid() == uid ) {
1036
kWarning() << "Failed to find Akonadi::Item for KCal uid " << uid;
1040
Akonadi::Item Calendar::itemForIncidenceUid( const QString &uid ) const
1042
return incidence( itemIdForIncidenceUid( uid ) );
1047
KCalCore::Person Calendar::owner() const
1052
void Calendar::setOwner( const KCalCore::Person &owner )
1057
void Calendar::setTimeSpec( const KDateTime::Spec &timeSpec )
1059
d->mTimeSpec = timeSpec;
1060
d->mBuiltInTimeZone = KCalCore::ICalTimeZone();
1061
setViewTimeSpec( timeSpec );
1063
doSetTimeSpec( d->mTimeSpec );
1066
KDateTime::Spec Calendar::timeSpec() const
1068
return d->mTimeSpec;
1071
void Calendar::setTimeZoneId( const QString &timeZoneId )
1073
d->mTimeSpec = d->timeZoneIdSpec( timeZoneId, false );
1074
d->mViewTimeSpec = d->mTimeSpec;
1075
d->mBuiltInViewTimeZone = d->mBuiltInTimeZone;
1077
doSetTimeSpec( d->mTimeSpec );
1081
KDateTime::Spec Calendar::Private::timeZoneIdSpec( const QString &timeZoneId,
1085
mBuiltInViewTimeZone = KCalCore::ICalTimeZone();
1087
mBuiltInTimeZone = KCalCore::ICalTimeZone();
1089
if ( timeZoneId == QLatin1String( "UTC" ) ) {
1090
return KDateTime::UTC;
1092
KCalCore::ICalTimeZone tz = mTimeZones->zone( timeZoneId );
1093
if ( !tz.isValid() ) {
1094
KCalCore::ICalTimeZoneSource tzsrc;
1095
#ifdef AKONADI_PORT_DISABLED
1096
tz = tzsrc.parse( icaltimezone_get_builtin_timezone( timeZoneId.toLatin1() ) );
1098
kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO;
1101
mBuiltInViewTimeZone = tz;
1103
mBuiltInTimeZone = tz;
1106
if ( tz.isValid() ) {
1109
return KDateTime::ClockTime;
1114
QString Calendar::timeZoneId() const
1116
KTimeZone tz = d->mTimeSpec.timeZone();
1117
return tz.isValid() ? tz.name() : QString();
1120
void Calendar::setViewTimeSpec( const KDateTime::Spec &timeSpec ) const
1122
d->mViewTimeSpec = timeSpec;
1123
d->mBuiltInViewTimeZone = KCalCore::ICalTimeZone();
1126
void Calendar::setViewTimeZoneId( const QString &timeZoneId ) const
1128
d->mViewTimeSpec = d->timeZoneIdSpec( timeZoneId, true );
1131
KDateTime::Spec Calendar::viewTimeSpec() const
1133
return d->mViewTimeSpec;
1136
QString Calendar::viewTimeZoneId() const
1138
KTimeZone tz = d->mViewTimeSpec.timeZone();
1139
return tz.isValid() ? tz.name() : QString();
1142
void Calendar::shiftTimes( const KDateTime::Spec &oldSpec,
1143
const KDateTime::Spec &newSpec )
1145
setTimeSpec( newSpec );
1147
Akonadi::Item::List ev = events();
1148
for ( i = 0, end = ev.count(); i < end; ++i ) {
1149
CalendarSupport::event( ev[i] )->shiftTimes( oldSpec, newSpec );
1152
Akonadi::Item::List to = todos();
1153
for ( i = 0, end = to.count(); i < end; ++i ) {
1154
CalendarSupport::todo( to[i] )->shiftTimes( oldSpec, newSpec );
1157
Akonadi::Item::List jo = journals();
1158
for ( i = 0, end = jo.count(); i < end; ++i ) {
1159
CalendarSupport::journal( jo[i] )->shiftTimes( oldSpec, newSpec );
1163
void Calendar::setFilter( KCalCore::CalFilter *filter )
1165
d->m_filterProxy->setFilter( filter ? filter : d->mDefaultFilter );
1168
KCalCore::CalFilter *Calendar::filter()
1170
return d->m_filterProxy->filter();
1173
QStringList Calendar::categories( Calendar *cal )
1175
Akonadi::Item::List rawInc( cal->rawIncidences() );
1176
QStringList cats, thisCats;
1177
// @TODO: For now just iterate over all incidences. In the future,
1178
// the list of categories should be built when reading the file.
1179
Q_FOREACH( const Akonadi::Item &i, rawInc ) {
1180
thisCats = CalendarSupport::incidence( i )->categories();
1181
for ( QStringList::ConstIterator si = thisCats.constBegin();
1182
si != thisCats.constEnd(); ++si ) {
1183
if ( !cats.contains( *si ) ) {
1191
Akonadi::Item::List Calendar::incidences( const QDate &date )
1193
return mergeIncidenceList( events( date ), todos( date ), journals( date ) );
1196
Akonadi::Item::List Calendar::incidences()
1198
if ( d->m_filterProxy->filter() == 0 || !d->m_filterProxy->filter()->isEnabled() ) {
1199
// Lets skip the filterProxy and return m_itemMap, which is cheaper.
1200
return rawIncidences();
1202
return itemsFromModel( d->m_filterProxy );
1206
Akonadi::Item::List Calendar::rawIncidences()
1208
// The following code is 100x faster than: return itemsFromModel( d->m_model )
1209
QHashIterator<Akonadi::Item::Id, Akonadi::Item> i( d->m_itemMap );
1210
Akonadi::Item::List list;
1211
while ( i.hasNext() ) {
1213
list.append( i.value() );
1219
Akonadi::Item::List Calendar::sortEvents( const Akonadi::Item::List &eventList_,
1220
EventSortField sortField,
1221
SortDirection sortDirection )
1223
Akonadi::Item::List eventList = eventList_;
1224
Akonadi::Item::List eventListSorted;
1225
Akonadi::Item::List tempList, t;
1226
Akonadi::Item::List alphaList;
1227
Akonadi::Item::List::Iterator sortIt;
1228
Akonadi::Item::List::Iterator eit;
1230
// Notice we alphabetically presort Summaries first.
1231
// We do this so comparison "ties" stay in a nice order.
1233
switch( sortField ) {
1234
case EventSortUnsorted:
1235
eventListSorted = eventList;
1238
case EventSortStartDate:
1239
alphaList = sortEvents( eventList, EventSortSummary, sortDirection );
1240
for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
1241
KCalCore::Event::Ptr e = CalendarSupport::event( *eit );
1243
if ( e->dtStart().isDateOnly() ) {
1244
tempList.append( *eit );
1247
sortIt = eventListSorted.begin();
1248
if ( sortDirection == SortDirectionAscending ) {
1249
while ( sortIt != eventListSorted.end() &&
1250
e->dtStart() >= CalendarSupport::event(*sortIt)->dtStart() ) {
1254
while ( sortIt != eventListSorted.end() &&
1255
e->dtStart() < CalendarSupport::event(*sortIt)->dtStart() ) {
1259
eventListSorted.insert( sortIt, *eit );
1261
if ( sortDirection == SortDirectionAscending ) {
1262
// Prepend the list of Events without End DateTimes
1263
tempList += eventListSorted;
1264
eventListSorted = tempList;
1266
// Append the list of Events without End DateTimes
1267
eventListSorted += tempList;
1271
case EventSortEndDate:
1272
alphaList = sortEvents( eventList, EventSortSummary, sortDirection );
1273
for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) {
1274
KCalCore::Event::Ptr e = CalendarSupport::event( *eit );
1276
if ( e->hasEndDate() ) {
1277
sortIt = eventListSorted.begin();
1278
if ( sortDirection == SortDirectionAscending ) {
1279
while ( sortIt != eventListSorted.end() &&
1280
e->dtEnd() >= CalendarSupport::event(*sortIt)->dtEnd() ) {
1284
while ( sortIt != eventListSorted.end() &&
1285
e->dtEnd() < CalendarSupport::event(*sortIt)->dtEnd() ) {
1290
// Keep a list of the Events without End DateTimes
1291
tempList.append( *eit );
1293
eventListSorted.insert( sortIt, *eit );
1295
if ( sortDirection == SortDirectionAscending ) {
1296
// Append the list of Events without End DateTimes
1297
eventListSorted += tempList;
1299
// Prepend the list of Events without End DateTimes
1300
tempList += eventListSorted;
1301
eventListSorted = tempList;
1305
case EventSortSummary:
1306
for ( eit = eventList.begin(); eit != eventList.end(); ++eit ) {
1307
KCalCore::Event::Ptr e = CalendarSupport::event( *eit );
1309
sortIt = eventListSorted.begin();
1310
if ( sortDirection == SortDirectionAscending ) {
1311
while ( sortIt != eventListSorted.end() &&
1312
e->summary() >= CalendarSupport::event(*sortIt)->summary() ) {
1316
while ( sortIt != eventListSorted.end() &&
1317
e->summary() < CalendarSupport::event(*sortIt)->summary() ) {
1321
eventListSorted.insert( sortIt, *eit );
1326
return eventListSorted;
1329
Akonadi::Item::List Calendar::events( const QDate &date,
1330
const KDateTime::Spec &timeSpec,
1331
EventSortField sortField,
1332
SortDirection sortDirection )
1334
const Akonadi::Item::List el = rawEventsForDate( date, timeSpec, sortField, sortDirection );
1335
return applyCalFilter( el, filter() );
1338
Akonadi::Item::List Calendar::events( const KDateTime &dt )
1340
const Akonadi::Item::List el = rawEventsForDate( dt );
1341
return applyCalFilter( el, filter() );
1344
Akonadi::Item::List Calendar::events( const QDate &start, const QDate &end,
1345
const KDateTime::Spec &timeSpec,
1348
const Akonadi::Item::List el = rawEvents( start, end, timeSpec, inclusive );
1349
return applyCalFilter( el, filter() );
1352
Akonadi::Item::List Calendar::events( EventSortField sortField,
1353
SortDirection sortDirection )
1355
const Akonadi::Item::List el = rawEvents( sortField, sortDirection );
1356
return applyCalFilter( el, filter() );
1359
KCalCore::Incidence::Ptr Calendar::dissociateOccurrence( const Akonadi::Item &item,
1361
const KDateTime::Spec &spec,
1364
if ( !item.isValid() ) {
1365
return KCalCore::Incidence::Ptr();
1368
const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
1369
if ( !incidence || !incidence->recurs() ) {
1370
return KCalCore::Incidence::Ptr();
1373
KCalCore::Incidence::Ptr newInc = KCalCore::Incidence::Ptr( incidence->clone() );
1375
// Do not call setRelatedTo() when dissociating recurring to-dos, otherwise the new to-do
1376
// will appear as a child. Originally, we planned to set a relation with reltype SIBLING
1377
// when dissociating to-dos, but currently kcal only supports reltype PARENT.
1378
// We can uncomment the following line when we support the PARENT reltype.
1379
//newInc->setRelatedTo( incidence );
1380
KCalCore::Recurrence *recur = newInc->recurrence();
1384
// Adjust the recurrence for the future incidences. In particular adjust
1385
// the "end after n occurrences" rules! "No end date" and "end by ..."
1386
// don't need to be modified.
1387
int duration = recur->duration();
1388
if ( duration > 0 ) {
1389
int doneduration = recur->durationTo( date.addDays( -1 ) );
1390
if ( doneduration >= duration ) {
1391
kDebug() << "The dissociated event already occurred more often"
1392
<< "than it was supposed to ever occur. ERROR!";
1395
recur->setDuration( duration - doneduration );
1399
// Adjust the date of the incidence
1400
if ( incidence->type() == KCalCore::IncidenceBase::TypeEvent ) {
1401
KCalCore::Event::Ptr ev = newInc.staticCast<KCalCore::Event>();
1402
KDateTime start( ev->dtStart() );
1403
int daysTo = start.toTimeSpec( spec ).date().daysTo( date );
1404
ev->setDtStart( start.addDays( daysTo ) );
1405
ev->setDtEnd( ev->dtEnd().addDays( daysTo ) );
1406
} else if ( incidence->type() == KCalCore::IncidenceBase::TypeTodo ) {
1407
KCalCore::Todo::Ptr td = newInc.staticCast<KCalCore::Todo>();
1408
bool haveOffset = false;
1410
if ( td->hasDueDate() ) {
1411
KDateTime due( td->dtDue() );
1412
daysTo = due.toTimeSpec( spec ).date().daysTo( date );
1413
td->setDtDue( due.addDays( daysTo ), true );
1416
if ( td->hasStartDate() ) {
1417
KDateTime start( td->dtStart() );
1418
if ( !haveOffset ) {
1419
daysTo = start.toTimeSpec( spec ).date().daysTo( date );
1421
td->setDtStart( start.addDays( daysTo ) );
1425
recur = incidence->recurrence();
1428
recur->addExDate( date );
1430
// Make sure the recurrence of the past events ends
1431
// at the corresponding day
1432
recur->setEndDate( date.addDays(-1) );
1435
return KCalCore::Incidence::Ptr( newInc );
1438
Akonadi::Item Calendar::incidence( Akonadi::Item::Id uid ) const
1440
Akonadi::Item i = event( uid );
1441
if ( i.isValid() ) {
1446
if ( i.isValid() ) {
1454
Akonadi::Item::List Calendar::incidencesFromSchedulingID( const QString &sid )
1456
Akonadi::Item::List result;
1457
const Akonadi::Item::List incidences = rawIncidences();
1458
Akonadi::Item::List::const_iterator it = incidences.begin();
1459
for ( ; it != incidences.end(); ++it ) {
1460
if ( CalendarSupport::incidence(*it)->schedulingID() == sid ) {
1461
result.append( *it );
1467
Akonadi::Item Calendar::incidenceFromSchedulingID( const QString &UID )
1469
const Akonadi::Item::List incidences = rawIncidences();
1470
Akonadi::Item::List::const_iterator it = incidences.begin();
1471
for ( ; it != incidences.end(); ++it ) {
1472
if ( CalendarSupport::incidence(*it)->schedulingID() == UID ) {
1473
// Touchdown, and the crowd goes wild
1478
return Akonadi::Item();
1481
Akonadi::Item::List Calendar::sortTodos( const Akonadi::Item::List &todoList_,
1482
TodoSortField sortField,
1483
SortDirection sortDirection )
1485
Akonadi::Item::List todoList( todoList_ );
1486
Akonadi::Item::List todoListSorted;
1487
Akonadi::Item::List tempList, t;
1488
Akonadi::Item::List alphaList;
1489
Akonadi::Item::List::Iterator sortIt;
1490
Akonadi::Item::List::ConstIterator eit;
1492
// Notice we alphabetically presort Summaries first.
1493
// We do this so comparison "ties" stay in a nice order.
1495
// Note that To-dos may not have Start DateTimes nor due DateTimes.
1497
switch( sortField ) {
1498
case TodoSortUnsorted:
1499
todoListSorted = todoList;
1502
case TodoSortStartDate:
1503
alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
1504
for ( eit = alphaList.constBegin(); eit != alphaList.constEnd(); ++eit ) {
1505
const KCalCore::Todo::Ptr e = CalendarSupport::todo( *eit );
1506
if ( e->hasStartDate() ) {
1507
sortIt = todoListSorted.begin();
1508
if ( sortDirection == SortDirectionAscending ) {
1509
while ( sortIt != todoListSorted.end() &&
1510
e->dtStart() >= CalendarSupport::todo(*sortIt)->dtStart() ) {
1514
while ( sortIt != todoListSorted.end() &&
1515
e->dtStart() < CalendarSupport::todo(*sortIt)->dtStart() ) {
1519
todoListSorted.insert( sortIt, *eit );
1521
// Keep a list of the To-dos without Start DateTimes
1522
tempList.append( *eit );
1525
if ( sortDirection == SortDirectionAscending ) {
1526
// Append the list of To-dos without Start DateTimes
1527
todoListSorted += tempList;
1529
// Prepend the list of To-dos without Start DateTimes
1530
tempList += todoListSorted;
1531
todoListSorted = tempList;
1535
case TodoSortDueDate:
1536
alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
1537
for ( eit = alphaList.constBegin(); eit != alphaList.constEnd(); ++eit ) {
1538
const KCalCore::Todo::Ptr e = CalendarSupport::todo( *eit );
1539
if ( e->hasDueDate() ) {
1540
sortIt = todoListSorted.begin();
1541
if ( sortDirection == SortDirectionAscending ) {
1542
while ( sortIt != todoListSorted.end() &&
1543
e->dtDue() >= CalendarSupport::todo( *sortIt )->dtDue() ) {
1547
while ( sortIt != todoListSorted.end() &&
1548
e->dtDue() < CalendarSupport::todo( *sortIt )->dtDue() ) {
1552
todoListSorted.insert( sortIt, *eit );
1554
// Keep a list of the To-dos without Due DateTimes
1555
tempList.append( *eit );
1558
if ( sortDirection == SortDirectionAscending ) {
1559
// Append the list of To-dos without Due DateTimes
1560
todoListSorted += tempList;
1562
// Prepend the list of To-dos without Due DateTimes
1563
tempList += todoListSorted;
1564
todoListSorted = tempList;
1568
case TodoSortPriority:
1569
alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
1570
for ( eit = alphaList.constBegin(); eit != alphaList.constEnd(); ++eit ) {
1571
const KCalCore::Todo::Ptr e = CalendarSupport::todo( *eit );
1572
sortIt = todoListSorted.begin();
1573
if ( sortDirection == SortDirectionAscending ) {
1574
while ( sortIt != todoListSorted.end() &&
1575
e->priority() >= CalendarSupport::todo(*sortIt)->priority() ) {
1579
while ( sortIt != todoListSorted.end() &&
1580
e->priority() < CalendarSupport::todo(*sortIt)->priority() ) {
1584
todoListSorted.insert( sortIt, *eit );
1588
case TodoSortPercentComplete:
1589
alphaList = sortTodos( todoList, TodoSortSummary, sortDirection );
1590
for ( eit = alphaList.constBegin(); eit != alphaList.constEnd(); ++eit ) {
1591
const KCalCore::Todo::Ptr e = CalendarSupport::todo( *eit );
1592
sortIt = todoListSorted.begin();
1593
if ( sortDirection == SortDirectionAscending ) {
1594
while ( sortIt != todoListSorted.end() &&
1595
e->percentComplete() >= CalendarSupport::todo(*sortIt)->percentComplete() ) {
1599
while ( sortIt != todoListSorted.end() &&
1600
e->percentComplete() < CalendarSupport::todo(*sortIt)->percentComplete() ) {
1604
todoListSorted.insert( sortIt, *eit );
1608
case TodoSortSummary:
1609
for ( eit = todoList.constBegin(); eit != todoList.constEnd(); ++eit ) {
1610
const KCalCore::Todo::Ptr e = CalendarSupport::todo( *eit );
1611
sortIt = todoListSorted.begin();
1612
if ( sortDirection == SortDirectionAscending ) {
1613
while ( sortIt != todoListSorted.end() &&
1614
e->summary() >= CalendarSupport::todo(*sortIt)->summary() ) {
1618
while ( sortIt != todoListSorted.end() &&
1619
e->summary() < CalendarSupport::todo(*sortIt)->summary() ) {
1623
todoListSorted.insert( sortIt, *eit );
1628
return todoListSorted;
1631
Akonadi::Item::List Calendar::todos( TodoSortField sortField,
1632
SortDirection sortDirection )
1634
const Akonadi::Item::List tl = rawTodos( sortField, sortDirection );
1635
return CalendarSupport::applyCalFilter( tl, filter() );
1638
Akonadi::Item::List Calendar::todos( const QDate &date )
1640
Akonadi::Item::List el = rawTodosForDate( date );
1641
return applyCalFilter( el, filter() );
1644
Akonadi::Item::List Calendar::sortJournals( const Akonadi::Item::List &journalList_,
1645
JournalSortField sortField,
1646
SortDirection sortDirection )
1648
Akonadi::Item::List journalList( journalList_ );
1649
Akonadi::Item::List journalListSorted;
1650
Akonadi::Item::List::Iterator sortIt;
1651
Akonadi::Item::List::ConstIterator eit;
1653
switch( sortField ) {
1654
case JournalSortUnsorted:
1655
journalListSorted = journalList;
1658
case JournalSortDate:
1659
for ( eit = journalList.constBegin(); eit != journalList.constEnd(); ++eit ) {
1660
const KCalCore::Journal::Ptr e = CalendarSupport::journal( *eit );
1661
sortIt = journalListSorted.begin();
1662
if ( sortDirection == SortDirectionAscending ) {
1663
while ( sortIt != journalListSorted.end() &&
1664
e->dtStart() >= CalendarSupport::journal(*sortIt)->dtStart() ) {
1668
while ( sortIt != journalListSorted.end() &&
1669
e->dtStart() < CalendarSupport::journal(*sortIt)->dtStart() ) {
1673
journalListSorted.insert( sortIt, *eit );
1677
case JournalSortSummary:
1678
for ( eit = journalList.constBegin(); eit != journalList.constEnd(); ++eit ) {
1679
const KCalCore::Journal::Ptr e = CalendarSupport::journal( *eit );
1680
sortIt = journalListSorted.begin();
1681
if ( sortDirection == SortDirectionAscending ) {
1682
while ( sortIt != journalListSorted.end() &&
1683
e->summary() >= CalendarSupport::journal(*sortIt)->summary() ) {
1687
while ( sortIt != journalListSorted.end() &&
1688
e->summary() < CalendarSupport::journal(*sortIt)->summary() ) {
1692
journalListSorted.insert( sortIt, *eit );
1697
return journalListSorted;
1700
Akonadi::Item::List Calendar::journals( JournalSortField sortField,
1701
SortDirection sortDirection )
1703
const Akonadi::Item::List jl = rawJournals( sortField, sortDirection );
1704
return CalendarSupport::applyCalFilter( jl, filter() );
1707
Akonadi::Item::List Calendar::journals( const QDate &date )
1709
Akonadi::Item::List el = rawJournalsForDate( date );
1710
return CalendarSupport::applyCalFilter( el, filter() );
1713
void Calendar::beginBatchAdding()
1715
emit batchAddingBegins();
1718
void Calendar::endBatchAdding()
1720
emit batchAddingEnds();
1723
#ifdef AKONADI_PORT_DISABLED
1725
void Calendar::setupRelations( const Akonadi::Item &forincidence )
1727
if ( !forincidence ) {
1731
QString uid = forincidence->uid();
1733
// First, go over the list of orphans and see if this is their parent
1734
QList<KCalCore::Incidence*> l = d->mOrphans.values( uid );
1735
d->mOrphans.remove( uid );
1736
for ( int i = 0, end = l.count(); i < end; ++i ) {
1737
forincidence->addRelation( l[i] );
1738
d->mOrphanUids.remove( l[i]->uid() );
1741
// Now see about this incidences parent
1742
if ( !forincidence->relatedTo() && !forincidence->relatedToUid().isEmpty() ) {
1743
// Incidence has a uid it is related to but is not registered to it yet.
1745
KCalCore::Incidence::Ptr parent = incidence( forincidence->relatedToUid() );
1747
// Not found, put this in the mOrphans list
1748
// Note that the mOrphans dict might contain multiple entries with the
1749
// same key! which are multiple children that wait for the parent
1750
// incidence to be inserted.
1751
d->mOrphans.insert( forincidence->relatedToUid(), forincidence );
1752
d->mOrphanUids.insert( forincidence->uid(), forincidence );
1756
#endif // AKONADI_PORT_DISABLED
1758
#ifdef AKONADI_PORT_DISABLED
1759
// If a to-do with sub-to-dos is deleted, move it's sub-to-dos to the orphan list
1760
void Calendar::removeRelations( const Akonadi::Item &incidence )
1763
kDebug() << "Warning: incidence is 0";
1767
QString uid = incidence->uid();
1768
foreach ( KCalCore::Incidence::Ptr i, incidence->relations() ) {
1769
if ( !d->mOrphanUids.contains( i->uid() ) ) {
1770
d->mOrphans.insert( uid, i );
1771
d->mOrphanUids.insert( i->uid(), i );
1772
i->setRelatedTo( uid );
1776
// If this incidence is related to something else, tell that about it
1777
if ( incidence->relatedTo() ) {
1778
incidence->relatedTo()->removeRelation( incidence );
1781
// Remove this one from the orphans list
1782
if ( d->mOrphanUids.remove( uid ) ) {
1783
// This incidence is located in the orphans list - it should be removed
1784
// Since the mOrphans dict might contain the same key (with different
1785
// child incidence pointers!) multiple times, take care that we remove
1786
// the correct one. So we need to remove all items with the given
1787
// parent UID, and readd those that are not for this item. Also, there
1788
// might be other entries with differnet UID that point to this
1789
// incidence (this might happen when the relatedTo of the item is
1790
// changed before its parent is inserted. This might happen with
1791
// groupware servers....). Remove them, too
1792
QStringList relatedToUids;
1794
// First, create a list of all keys in the mOrphans list which point
1795
// to the removed item
1796
relatedToUids << incidence->relatedToUid();
1797
for ( QMultiHash<QString, Incidence*>::Iterator it = d->mOrphans.begin();
1798
it != d->mOrphans.end(); ++it ) {
1799
if ( it.value()->uid() == uid ) {
1800
relatedToUids << it.key();
1804
// now go through all uids that have one entry that point to the incidence
1805
for ( QStringList::const_iterator uidit = relatedToUids.constBegin();
1806
uidit != relatedToUids.constEnd(); ++uidit ) {
1807
Incidence::List tempList;
1808
// Remove all to get access to the remaining entries
1809
QList<KCalCore::Incidence*> l = d->mOrphans.values( *uidit );
1810
d->mOrphans.remove( *uidit );
1811
foreach ( Incidence *i, l ) {
1812
if ( i != incidence ) {
1813
tempList.append( i );
1816
// Readd those that point to a different orphan incidence
1817
for ( KCalCore::Incidence::List::Iterator incit = tempList.begin();
1818
incit != tempList.end(); ++incit ) {
1819
d->mOrphans.insert( *uidit, *incit );
1824
// Make sure the deleted incidence doesn't relate to a non-deleted incidence,
1825
// since that would cause trouble in CalendarLocal::close(), as the deleted
1826
// incidences are destroyed after the non-deleted incidences. The destructor
1827
// of the deleted incidences would then try to access the already destroyed
1828
// non-deleted incidence, which would segfault.
1830
// So in short: Make sure dead incidences don't point to alive incidences
1831
// via the relation.
1833
// This crash is tested in CalendarLocalTest::testRelationsCrash().
1835
#endif // AKONADI_PORT_DISABLED
1837
void Calendar::CalendarObserver::calendarIncidenceAdded( const Akonadi::Item &incidence )
1839
Q_UNUSED( incidence );
1842
void Calendar::CalendarObserver::calendarIncidenceChanged( const Akonadi::Item &incidence )
1844
Q_UNUSED( incidence );
1847
void Calendar::CalendarObserver::calendarIncidenceDeleted( const Akonadi::Item &incidence )
1849
Q_UNUSED( incidence );
1852
void Calendar::registerObserver( CalendarObserver *observer )
1854
if ( !d->mObservers.contains( observer ) ) {
1855
d->mObservers.append( observer );
1857
d->mNewObserver = true;
1860
void Calendar::unregisterObserver( CalendarObserver *observer )
1862
d->mObservers.removeAll( observer );
1865
void Calendar::doSetTimeSpec( const KDateTime::Spec &timeSpec )
1867
Q_UNUSED( timeSpec );
1870
void Calendar::notifyIncidenceAdded( const Akonadi::Item &i )
1872
if ( !d->mObserversEnabled ) {
1876
foreach ( CalendarObserver *observer, d->mObservers ) {
1877
observer->calendarIncidenceAdded( i );
1881
void Calendar::notifyIncidenceChanged( const Akonadi::Item &i )
1883
if ( !d->mObserversEnabled ) {
1887
foreach ( CalendarObserver *observer, d->mObservers ) {
1888
observer->calendarIncidenceChanged( i );
1892
void Calendar::notifyIncidenceDeleted( const Akonadi::Item &i )
1894
if ( !d->mObserversEnabled ) {
1898
foreach ( CalendarObserver *observer, d->mObservers ) {
1899
observer->calendarIncidenceDeleted( i );
1903
void Calendar::customPropertyUpdated()
1907
void Calendar::setProductId( const QString &id )
1912
QString Calendar::productId() const
1914
return d->mProductId;
1917
Akonadi::Item::List Calendar::mergeIncidenceList( const Akonadi::Item::List &events,
1918
const Akonadi::Item::List &todos,
1919
const Akonadi::Item::List &journals )
1921
Akonadi::Item::List incidences;
1924
for ( i = 0, end = events.count(); i < end; ++i ) {
1925
incidences.append( events[i] );
1928
for ( i = 0, end = todos.count(); i < end; ++i ) {
1929
incidences.append( todos[i] );
1932
for ( i = 0, end = journals.count(); i < end; ++i ) {
1933
incidences.append( journals[i] );
1939
void Calendar::setObserversEnabled( bool enabled )
1941
d->mObserversEnabled = enabled;
1944
void Calendar::appendAlarms( KCalCore::Alarm::List &alarms, const Akonadi::Item &item,
1945
const KDateTime &from, const KDateTime &to )
1947
const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
1948
Q_ASSERT( incidence );
1950
KDateTime preTime = from.addSecs(-1);
1952
KCalCore::Alarm::List alarmlist = incidence->alarms();
1953
for ( int i = 0, iend = alarmlist.count(); i < iend; ++i ) {
1954
if ( alarmlist[i]->enabled() ) {
1955
KDateTime dt = alarmlist[i]->nextRepetition( preTime );
1956
if ( dt.isValid() && dt <= to ) {
1957
kDebug() << incidence->summary() << "':" << dt.toString();
1958
alarms.append( alarmlist[i] );
1964
void Calendar::appendRecurringAlarms( KCalCore::Alarm::List &alarms,
1965
const Akonadi::Item &item,
1966
const KDateTime &from,
1967
const KDateTime &to )
1970
bool endOffsetValid = false;
1971
KCalCore::Duration endOffset( 0 );
1972
KCalCore::Duration period( from, to );
1974
const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item );
1975
Q_ASSERT( incidence );
1977
KCalCore::Alarm::List alarmlist = incidence->alarms();
1978
for ( int i = 0, iend = alarmlist.count(); i < iend; ++i ) {
1979
KCalCore::Alarm::Ptr a = alarmlist[i];
1980
if ( a->enabled() ) {
1981
if ( a->hasTime() ) {
1982
// The alarm time is defined as an absolute date/time
1983
dt = a->nextRepetition( from.addSecs(-1) );
1984
if ( !dt.isValid() || dt > to ) {
1988
// Alarm time is defined by an offset from the event start or end time.
1989
// Find the offset from the event start time, which is also used as the
1990
// offset from the recurrence time.
1991
KCalCore::Duration offset( 0 );
1992
if ( a->hasStartOffset() ) {
1993
offset = a->startOffset();
1994
} else if ( a->hasEndOffset() ) {
1995
offset = a->endOffset();
1996
if ( !endOffsetValid ) {
1997
endOffset = KCalCore::Duration(
1998
incidence->dtStart(),
1999
incidence->dateTime( KCalCore::IncidenceBase::RoleAlarmEndOffset ) );
2000
endOffsetValid = true;
2004
// Find the incidence's earliest alarm
2005
KDateTime alarmStart =
2006
offset.end( a->hasEndOffset() ?
2007
incidence->dateTime( KCalCore::IncidenceBase::RoleAlarmEndOffset ) :
2008
incidence->dtStart() );
2009
// KDateTime alarmStart = incidence->dtStart().addSecs( offset );
2010
if ( alarmStart > to ) {
2013
KDateTime baseStart = incidence->dtStart();
2014
if ( from > alarmStart ) {
2015
alarmStart = from; // don't look earlier than the earliest alarm
2016
baseStart = (-offset).end( (-endOffset).end( alarmStart ) );
2019
// Adjust the 'alarmStart' date/time and find the next recurrence at or after it.
2020
// Treate the two offsets separately in case one is daily and the other not.
2021
dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs(-1) );
2022
if ( !dt.isValid() ||
2023
( dt = endOffset.end( offset.end( dt ) ) ) > to ) // adjust 'dt' to get the alarm time
2025
// The next recurrence is too late.
2026
if ( !a->repeatCount() ) {
2030
// The alarm has repetitions, so check whether repetitions of previous
2031
// recurrences fall within the time period.
2033
KCalCore::Duration alarmDuration = a->duration();
2034
for ( KDateTime base = baseStart;
2035
( dt = incidence->recurrence()->getPreviousDateTime( base ) ).isValid();
2037
if ( a->duration().end( dt ) < base ) {
2038
break; // this recurrence's last repetition is too early, so give up
2041
// The last repetition of this recurrence is at or after 'alarmStart' time.
2042
// Check if a repetition occurs between 'alarmStart' and 'to'.
2043
int snooze = a->snoozeTime().value(); // in seconds or days
2044
if ( a->snoozeTime().isDaily() ) {
2045
KCalCore::Duration toFromDuration( dt, base );
2046
int toFrom = toFromDuration.asDays();
2047
if ( a->snoozeTime().end( from ) <= to ||
2048
( toFromDuration.isDaily() && toFrom % snooze == 0 ) ||
2049
( toFrom / snooze + 1 ) * snooze <= toFrom + period.asDays() ) {
2053
dt = offset.end( dt ).addDays( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
2058
int toFrom = dt.secsTo( base );
2059
if ( period.asSeconds() >= snooze ||
2060
toFrom % snooze == 0 ||
2061
( toFrom / snooze + 1 ) * snooze <= toFrom + period.asSeconds() )
2066
dt = offset.end( dt ).addSecs( ( ( toFrom - 1 ) / snooze + 1 ) * snooze );
2077
// kDebug() << incidence->summary() << "':" << dt.toString();
2083
Akonadi::Collection Calendar::collection( const Akonadi::Collection::Id &id ) const
2085
if ( d->m_collectionMap.contains( id ) ) {
2086
return d->m_collectionMap[id];
2088
return Akonadi::Collection();
2092
bool Calendar::hasChangeRights( const Akonadi::Item &item ) const
2094
// if the users changes the rights, item.parentCollection()
2095
// can still have the old rights, so we use call collection()
2096
// which returns the updated one
2097
const Akonadi::Collection col = collection( item.storageCollectionId() );
2098
return col.rights() & Akonadi::Collection::CanChangeItem;
2101
bool Calendar::hasDeleteRights( const Akonadi::Item &item ) const
2103
// if the users changes the rights, item.parentCollection()
2104
// can still have the old rights, so we use call collection()
2105
// which returns the updated one
2106
const Akonadi::Collection col = collection( item.storageCollectionId() );
2107
return col.rights() & Akonadi::Collection::CanDeleteItem;
2110
int Calendar::incidencesCount() const
2112
return d->m_model->rowCount();