1 #warning Read-only resource endlessly triggers alarm, disabling does not stop this
4 * akonadimodel.cpp - KAlarm calendar file access using Akonadi
6 * Copyright © 2007-2010 by David Jarvie <djarvie@kde.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include "akonadimodel.h"
26 #include "alarmtext.h"
27 #include "autoqpointer.h"
28 #include "collectionattribute.h"
29 #include "eventattribute.h"
31 #include "preferences.h"
32 #include "synchtimer.h"
34 #include <akonadi/attributefactory.h>
35 #include <akonadi/changerecorder.h>
36 #include <akonadi/collectiondialog.h>
37 #include <akonadi/collectionmodifyjob.h>
38 #include <akonadi/entitydisplayattribute.h>
39 #include <akonadi/item.h>
40 #include <akonadi/itemcreatejob.h>
41 #include <akonadi/itemmodifyjob.h>
42 #include <akonadi/itemdeletejob.h>
43 #include <akonadi/itemfetchscope.h>
44 #include <akonadi/session.h>
45 #include <kcal/calendarlocal.h>
48 #include <kmessagebox.h>
50 #include <QApplication>
52 #include <QMouseEvent>
58 Q_DECLARE_METATYPE(KAEvent
)
60 using namespace Akonadi
;
61 using KAlarm::CollectionAttribute
;
62 using KAlarm::EventAttribute
;
64 static Collection::Rights writableRights
= Collection::CanChangeItem
| Collection::CanCreateItem
| Collection::CanDeleteItem
;
66 static bool checkItem_true(const Item
&) { return true; }
68 /*=============================================================================
70 =============================================================================*/
72 AkonadiModel
* AkonadiModel::mInstance
= 0;
73 QPixmap
* AkonadiModel::mTextIcon
= 0;
74 QPixmap
* AkonadiModel::mFileIcon
= 0;
75 QPixmap
* AkonadiModel::mCommandIcon
= 0;
76 QPixmap
* AkonadiModel::mEmailIcon
= 0;
77 QPixmap
* AkonadiModel::mAudioIcon
= 0;
78 QSize
AkonadiModel::mIconSize
;
79 int AkonadiModel::mTimeHourPos
= -2;
81 /******************************************************************************
82 * Construct and return the singleton.
84 AkonadiModel
* AkonadiModel::instance()
87 mInstance
= new AkonadiModel(new ChangeRecorder(qApp
), qApp
);
91 /******************************************************************************
94 AkonadiModel::AkonadiModel(ChangeRecorder
* monitor
, QObject
* parent
)
95 : EntityTreeModel(monitor
, parent
)
97 // Set lazy population to enable the contents of unselected collections to be ignored
98 setItemPopulationStrategy(LazyPopulation
);
100 // Restrict monitoring to collections containing the KAlarm mime types
101 monitor
->setCollectionMonitored(Collection::root());
102 monitor
->setResourceMonitored("akonadi_kalarm_resource");
103 monitor
->setMimeTypeMonitored(KAlarm::MIME_ACTIVE
);
104 monitor
->setMimeTypeMonitored(KAlarm::MIME_ARCHIVED
);
105 monitor
->setMimeTypeMonitored(KAlarm::MIME_TEMPLATE
);
106 monitor
->itemFetchScope().fetchFullPayload();
108 AttributeFactory::registerAttribute
<CollectionAttribute
>();
109 AttributeFactory::registerAttribute
<EventAttribute
>();
113 mTextIcon
= new QPixmap(SmallIcon("dialog-information"));
114 mFileIcon
= new QPixmap(SmallIcon("document-open"));
115 mCommandIcon
= new QPixmap(SmallIcon("system-run"));
116 mEmailIcon
= new QPixmap(SmallIcon("mail-message-unread"));
117 mAudioIcon
= new QPixmap(SmallIcon("audio-x-generic"));
118 mIconSize
= mTextIcon
->size().expandedTo(mFileIcon
->size()).expandedTo(mCommandIcon
->size()).expandedTo(mEmailIcon
->size()).expandedTo(mAudioIcon
->size());
122 #warning Only want to monitor collection properties, not content, when this becomes possible
124 connect(monitor
, SIGNAL(collectionChanged(const Akonadi::Collection
&, const QSet
<QByteArray
>&)), SLOT(slotCollectionChanged(const Akonadi::Collection
&, const QSet
<QByteArray
>&)));
125 connect(monitor
, SIGNAL(collectionRemoved(const Akonadi::Collection
&)), SLOT(slotCollectionRemoved(const Akonadi::Collection
&)));
126 MinuteTimer::connect(this, SLOT(slotUpdateTimeTo()));
127 Preferences::connect(SIGNAL(archivedColourChanged(const QColor
&)), this, SLOT(slotUpdateArchivedColour(const QColor
&)));
128 Preferences::connect(SIGNAL(disabledColourChanged(const QColor
&)), this, SLOT(slotUpdateDisabledColour(const QColor
&)));
129 Preferences::connect(SIGNAL(holidaysChanged(const KHolidays::HolidayRegion
&)), this, SLOT(slotUpdateHolidays()));
130 Preferences::connect(SIGNAL(workTimeChanged(const QTime
&, const QTime
&, const QBitArray
&)), this, SLOT(slotUpdateWorkingHours()));
132 connect(this, SIGNAL(rowsInserted(const QModelIndex
&, int, int)), SLOT(slotRowsInserted(const QModelIndex
&, int, int)));
133 connect(this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex
&, int, int)), SLOT(slotRowsAboutToBeRemoved(const QModelIndex
&, int, int)));
134 connect(monitor
, SIGNAL(itemChanged(const Akonadi::Item
&, const QSet
<QByteArray
>&)), SLOT(slotMonitoredItemChanged(const Akonadi::Item
&, const QSet
<QByteArray
>&)));
137 /******************************************************************************
138 * Return the data for a given role, for a specified item.
140 QVariant
AkonadiModel::data(const QModelIndex
& index
, int role
) const
142 // First check that it's a role we're interested in - if not, use the base method
145 case Qt::BackgroundRole
:
146 case Qt::ForegroundRole
:
147 case Qt::DisplayRole
:
148 case Qt::TextAlignmentRole
:
149 case Qt::DecorationRole
:
150 case Qt::SizeHintRole
:
151 case Qt::AccessibleTextRole
:
152 case Qt::ToolTipRole
:
153 case Qt::CheckStateRole
:
158 case AlarmActionsRole
:
159 case AlarmActionRole
:
161 case CommandErrorRole
:
166 return EntityTreeModel::data(index
, role
);
169 const Collection collection
= index
.data(CollectionRole
).value
<Collection
>();
170 if (collection
.isValid())
172 // This is a Collection row
175 case Qt::DisplayRole
:
176 return displayName(collection
);
178 if (collection
.hasAttribute
<CollectionAttribute
>())
179 return collection
.attribute
<CollectionAttribute
>()->isEnabled();
181 case Qt::BackgroundRole
:
183 QColor colour
= backgroundColor(collection
);
184 if (colour
.isValid())
188 case Qt::ForegroundRole
:
190 QStringList mimeTypes
= collection
.contentMimeTypes();
191 if (mimeTypes
.contains(KAlarm::MIME_ACTIVE
))
192 return (collection
.rights() & writableRights
) == writableRights
? Qt::black
: Qt::darkGray
;
193 if (mimeTypes
.contains(KAlarm::MIME_ARCHIVED
))
194 return (collection
.rights() & writableRights
) == writableRights
? Qt::darkGreen
: Qt::green
;
195 if (mimeTypes
.contains(KAlarm::MIME_TEMPLATE
))
196 return (collection
.rights() & writableRights
) == writableRights
? Qt::darkBlue
: Qt::blue
;
201 if (!collection
.hasAttribute
<CollectionAttribute
>())
203 CollectionAttribute
* attr
= collection
.attribute
<CollectionAttribute
>();
204 if (!attr
->isEnabled())
206 QStringList mimeTypes
= collection
.contentMimeTypes();
207 if ((mimeTypes
.contains(KAlarm::MIME_ACTIVE
) && attr
->isStandard(KAlarm::CalEvent::ACTIVE
))
208 || (mimeTypes
.contains(KAlarm::MIME_ARCHIVED
) && attr
->isStandard(KAlarm::CalEvent::ARCHIVED
))
209 || (mimeTypes
.contains(KAlarm::MIME_TEMPLATE
) && attr
->isStandard(KAlarm::CalEvent::TEMPLATE
)))
211 // It's the standard collection for a mime type
218 case Qt::ToolTipRole
:
220 QString name
= '@' + displayName(collection
); // insert markers for stripping out name
221 KUrl url
= collection
.remoteId();
222 QString type
= '@' + storageType(collection
); // file/directory/URL etc.
223 QString locn
= url
.pathOrUrl();
224 bool inactive
= !collection
.hasAttribute
<CollectionAttribute
>()
225 || !collection
.attribute
<CollectionAttribute
>()->isEnabled();
226 bool writable
= (collection
.rights() & writableRights
) == writableRights
;
227 QString disabled
= i18nc("@info/plain", "Disabled");
228 QString readonly
= i18nc("@info/plain", "Read-only");
229 //if (!collection.hasAttribute<CollectionAttribute>()) { kDebug()<<"Tooltip: no collection attribute"; } else { kDebug()<<"Tooltip: enabled="<<collection.attribute<CollectionAttribute>()->isEnabled(); } //disabled="<<inactive;
230 if (inactive
&& !writable
)
231 return i18nc("@info:tooltip",
233 "<nl/>%2: <filename>%3</filename>"
235 name
, type
, locn
, disabled
, readonly
);
236 if (inactive
|| !writable
)
237 return i18nc("@info:tooltip",
239 "<nl/>%2: <filename>%3</filename>"
241 name
, type
, locn
, (inactive
? disabled
: readonly
));
242 return i18nc("@info:tooltip",
244 "<nl/>%2: <filename>%3</filename>",
248 return static_cast<int>(types(collection
));
250 if (!collection
.hasAttribute
<CollectionAttribute
>())
252 return static_cast<int>(collection
.attribute
<CollectionAttribute
>()->standard());
259 const Item item
= index
.data(ItemRole
).value
<Item
>();
262 // This is an Item row
263 QString mime
= item
.mimeType();
264 if ((mime
!= KAlarm::MIME_ACTIVE
&& mime
!= KAlarm::MIME_ARCHIVED
&& mime
!= KAlarm::MIME_TEMPLATE
)
265 || !item
.hasPayload
<KAEvent
>())
270 // Mime type has a one-to-one relationship to event's category()
271 if (mime
== KAlarm::MIME_ACTIVE
)
272 return KAlarm::CalEvent::ACTIVE
;
273 if (mime
== KAlarm::MIME_ARCHIVED
)
274 return KAlarm::CalEvent::ARCHIVED
;
275 if (mime
== KAlarm::MIME_TEMPLATE
)
276 return KAlarm::CalEvent::TEMPLATE
;
278 case CommandErrorRole
:
279 if (!item
.hasAttribute
<EventAttribute
>())
280 return KAEvent::CMD_NO_ERROR
;
281 return item
.attribute
<EventAttribute
>()->commandError();
285 int column
= index
.column();
286 if (role
== Qt::WhatsThisRole
)
287 return whatsThisText(column
);
288 KAEvent event
= item
.payload
<KAEvent
>();
289 if (!event
.isValid())
291 if (role
== AlarmActionsRole
)
292 return event
.actions();
293 if (role
== AlarmActionRole
)
294 return event
.action();
295 bool calendarColour
= false;
301 case Qt::BackgroundRole
:
302 calendarColour
= true;
304 case Qt::DisplayRole
:
306 return alarmTimeText(event
.startDateTime());
307 return alarmTimeText(event
.nextTrigger(KAEvent::DISPLAY_TRIGGER
));
312 due
= event
.startDateTime();
314 due
= event
.nextTrigger(KAEvent::DISPLAY_TRIGGER
);
315 return due
.isValid() ? due
.effectiveKDateTime().toUtc().dateTime()
316 : QDateTime(QDate(9999,12,31), QTime(0,0,0));
325 case Qt::BackgroundRole
:
326 calendarColour
= true;
328 case Qt::DisplayRole
:
331 return timeToAlarmText(event
.nextTrigger(KAEvent::DISPLAY_TRIGGER
));
336 DateTime due
= event
.nextTrigger(KAEvent::DISPLAY_TRIGGER
);
337 KDateTime now
= KDateTime::currentUtcDateTime();
338 if (due
.isDateOnly())
339 return now
.date().daysTo(due
.date()) * 1440;
340 return (now
.secsTo(due
.effectiveKDateTime()) + 59) / 60;
347 case Qt::BackgroundRole
:
348 calendarColour
= true;
350 case Qt::DisplayRole
:
351 return repeatText(event
);
352 case Qt::TextAlignmentRole
:
353 return Qt::AlignHCenter
;
355 return repeatOrder(event
);
361 case Qt::BackgroundRole
:
362 if (event
.action() == KAEvent::MESSAGE
363 || event
.action() == KAEvent::FILE
364 || (event
.action() == KAEvent::COMMAND
&& event
.commandDisplay()))
365 return event
.bgColour();
366 if (event
.action() == KAEvent::COMMAND
)
368 if (event
.commandError() != KAEvent::CMD_NO_ERROR
)
372 case Qt::ForegroundRole
:
373 if (event
.commandError() != KAEvent::CMD_NO_ERROR
)
375 if (event
.action() == KAEvent::COMMAND
&& !event
.commandDisplay())
377 QColor colour
= Qt::red
;
379 event
.bgColour().getRgb(&r
, &g
, &b
);
380 if (r
> 128 && g
<= 128 && b
<= 128)
385 case Qt::DisplayRole
:
386 if (event
.commandError() != KAEvent::CMD_NO_ERROR
)
387 return QString::fromLatin1("!");
391 unsigned i
= (event
.action() == KAEvent::MESSAGE
|| event
.action() == KAEvent::FILE)
392 ? event
.bgColour().rgb() : 0;
393 return QString("%1").arg(i
, 6, 10, QLatin1Char('0'));
402 case Qt::DecorationRole
:
405 v
.setValue(*eventIcon(event
));
408 case Qt::TextAlignmentRole
:
409 return Qt::AlignHCenter
;
410 case Qt::SizeHintRole
:
412 case Qt::AccessibleTextRole
:
414 #warning Implement this
418 return static_cast<int>(event
.action());
420 return QString("%1").arg(event
.action(), 2, 10, QLatin1Char('0'));
426 case Qt::BackgroundRole
:
427 calendarColour
= true;
429 case Qt::DisplayRole
:
431 return AlarmText::summary(event
, 1);
432 case Qt::ToolTipRole
:
433 return AlarmText::summary(event
);
438 case TemplateNameColumn
:
441 case Qt::BackgroundRole
:
442 calendarColour
= true;
444 case Qt::DisplayRole
:
445 return event
.templateName();
447 return event
.templateName().toUpper();
456 case Qt::ForegroundRole
:
457 if (!event
.enabled())
458 return Preferences::disabledColour();
460 return Preferences::archivedColour();
461 break; // use the default for normal active alarms
462 case Qt::ToolTipRole
:
463 // Show the last command execution error message
464 switch (event
.commandError())
466 case KAEvent::CMD_ERROR
:
467 return i18nc("@info:tooltip", "Command execution failed");
468 case KAEvent::CMD_ERROR_PRE
:
469 return i18nc("@info:tooltip", "Pre-alarm action execution failed");
470 case KAEvent::CMD_ERROR_POST
:
471 return i18nc("@info:tooltip", "Post-alarm action execution failed");
472 case KAEvent::CMD_ERROR_PRE_POST
:
473 return i18nc("@info:tooltip", "Pre- and post-alarm action execution failed");
475 case KAEvent::CMD_NO_ERROR
:
480 return event
.enabled();
487 QColor colour
= backgroundColor(item
.parentCollection());
488 if (colour
.isValid())
493 return EntityTreeModel::data(index
, role
);
496 /******************************************************************************
497 * Set the font to use for all items, or the checked state of one item.
498 * The font must always be set at initialisation.
500 bool AkonadiModel::setData(const QModelIndex
& index
, const QVariant
& value
, int role
)
502 if (!index
.isValid())
504 Collection collection
= index
.data(CollectionRole
).value
<Collection
>();
505 if (collection
.isValid())
507 // This is a Collection row
508 bool updateCollection
= false;
512 // Set the font used in all views.
513 // This enables data(index, Qt::FontRole) to return bold when appropriate.
514 mFont
= value
.value
<QFont
>();
518 bool enabled
= value
.toBool();
519 CollectionAttribute
* attr
= collection
.attribute
<CollectionAttribute
>(Entity::AddIfMissing
);
520 if (attr
) { kDebug()<<"Set:"<<enabled
<<", was="<<attr
->isEnabled(); } else { kDebug()<<"Set:"<<enabled
<<", no attribute"; }
521 if (attr
->isEnabled() == enabled
)
523 attr
->setEnabled(enabled
);
524 updateCollection
= true;
528 if (collection
.hasAttribute
<CollectionAttribute
>())
530 KAlarm::CalEvent::Types types
= static_cast<KAlarm::CalEvent::Types
>(value
.value
<int>());
531 collection
.attribute
<CollectionAttribute
>()->setStandard(types
);
532 updateCollection
= true;
538 if (updateCollection
)
540 CollectionModifyJob
* job
= new CollectionModifyJob(collection
, this);
541 connect(job
, SIGNAL(result(KJob
*)), this, SLOT(collectionJobDone(KJob
*)));
547 Item item
= index
.data(ItemRole
).value
<Item
>();
550 bool updateItem
= false;
555 #warning ??? update event
556 int row
= index
.row();
557 emit
dataChanged(this->index(row
, 0, index
.parent()), this->index(row
, ColumnCount
- 1, index
.parent()));
560 case CommandErrorRole
:
562 KAEvent::CmdErrType err
= static_cast<KAEvent::CmdErrType
>(value
.toInt());
565 case KAEvent::CMD_NO_ERROR
:
566 case KAEvent::CMD_ERROR
:
567 case KAEvent::CMD_ERROR_PRE
:
568 case KAEvent::CMD_ERROR_POST
:
569 case KAEvent::CMD_ERROR_PRE_POST
:
570 if (err
== KAEvent::CMD_NO_ERROR
&& !item
.hasAttribute
<EventAttribute
>())
572 EventAttribute
* attr
= item
.attribute
<EventAttribute
>(Entity::AddIfMissing
);
573 if (attr
->commandError() == err
)
575 attr
->setCommandError(err
);
577 // int row = index.row();
578 // emit dataChanged(this->index(row, 0, index.parent()), this->index(row, ColumnCount - 1, index.parent()));
588 ItemModifyJob
* job
= new ItemModifyJob(item
, this);
589 connect(job
, SIGNAL(result(KJob
*)), this, SLOT(itemJobDone(KJob
*)));
594 return EntityTreeModel::setData(index
, value
, role
);
597 /******************************************************************************
598 * Return the number of columns for either a collection or an item.
600 int AkonadiModel::entityColumnCount(HeaderGroup group
) const
604 case CollectionTreeHeaders
:
606 case ItemListHeaders
:
609 return EntityTreeModel::entityColumnCount(group
);
613 /******************************************************************************
614 * Return data for a column heading.
616 QVariant
AkonadiModel::entityHeaderData(int section
, Qt::Orientation orientation
, int role
, HeaderGroup group
) const
618 if (orientation
== Qt::Horizontal
)
622 case CollectionTreeHeaders
:
625 if (role
== Qt::DisplayRole
)
626 return i18nc("@title:column", "Calendars");
629 case ItemListHeaders
:
630 if (section
< 0 || section
>= ColumnCount
)
632 if (role
== Qt::DisplayRole
)
637 return i18nc("@title:column", "Time");
639 return i18nc("@title:column", "Time To");
641 return i18nc("@title:column", "Repeat");
647 return i18nc("@title:column", "Message, File or Command");
648 case TemplateNameColumn
:
649 return i18nc("@title:column Template name", "Name");
652 else if (role
== Qt::WhatsThisRole
)
653 return whatsThisText(section
);
660 return EntityTreeModel::entityHeaderData(section
, orientation
, role
, group
);
663 /******************************************************************************
664 * Return the alarm time text in the form "date time".
666 QString
AkonadiModel::alarmTimeText(const DateTime
& dateTime
) const
668 if (!dateTime
.isValid())
669 return i18nc("@info/plain Alarm never occurs", "Never");
670 KLocale
* locale
= KGlobal::locale();
671 KDateTime kdt
= dateTime
.effectiveKDateTime().toTimeSpec(Preferences::timeZone());
672 QString dateTimeText
= locale
->formatDate(kdt
.date(), KLocale::ShortDate
);
673 if (!dateTime
.isDateOnly()
674 || (!dateTime
.isClockTime() && kdt
.utcOffset() != dateTime
.utcOffset()))
676 // Display the time of day if it's a date/time value, or if it's
677 // a date-only value but it's in a different time zone
678 dateTimeText
+= QLatin1Char(' ');
679 QString time
= locale
->formatTime(kdt
.time());
680 if (mTimeHourPos
== -2)
682 // Initialise the position of the hour within the time string, if leading
683 // zeroes are omitted, so that displayed times can be aligned with each other.
684 mTimeHourPos
= -1; // default = alignment isn't possible/sensible
685 if (QApplication::isLeftToRight()) // don't try to align right-to-left languages
687 QString fmt
= locale
->timeFormat();
688 int i
= fmt
.indexOf(QRegExp("%[kl]")); // check if leading zeroes are omitted
689 if (i
>= 0 && i
== fmt
.indexOf(QLatin1Char('%'))) // and whether the hour is first
690 mTimeHourPos
= i
; // yes, so need to align
693 if (mTimeHourPos
>= 0 && (int)time
.length() > mTimeHourPos
+ 1
694 && time
[mTimeHourPos
].isDigit() && !time
[mTimeHourPos
+ 1].isDigit())
695 dateTimeText
+= QLatin1Char('~'); // improve alignment of times with no leading zeroes
696 dateTimeText
+= time
;
698 return dateTimeText
+ QLatin1Char(' ');
701 /******************************************************************************
702 * Return the time-to-alarm text.
704 QString
AkonadiModel::timeToAlarmText(const DateTime
& dateTime
) const
706 if (!dateTime
.isValid())
707 return i18nc("@info/plain Alarm never occurs", "Never");
708 KDateTime now
= KDateTime::currentUtcDateTime();
709 if (dateTime
.isDateOnly())
711 int days
= now
.date().daysTo(dateTime
.date());
712 // xgettext: no-c-format
713 return i18nc("@info/plain n days", "%1d", days
);
715 int mins
= (now
.secsTo(dateTime
.effectiveKDateTime()) + 59) / 60;
718 char minutes
[3] = "00";
719 minutes
[0] = (mins
%60) / 10 + '0';
720 minutes
[1] = (mins
%60) % 10 + '0';
722 return i18nc("@info/plain hours:minutes", "%1:%2", mins
/60, minutes
);
723 int days
= mins
/ (24*60);
724 mins
= mins
% (24*60);
725 return i18nc("@info/plain days hours:minutes", "%1d %2:%3", days
, mins
/60, minutes
);
728 /******************************************************************************
729 * Recursive function to emit the dataChanged() signal for all items with a
730 * given mime type and in a specified column range.
732 void AkonadiModel::signalDataChanged(bool (*checkFunc
)(const Item
&), int startColumn
, int endColumn
, const QModelIndex
& parent
)
736 for (int row
= 0, count
= rowCount(parent
); row
< count
; ++row
)
738 const QModelIndex ix
= index(row
, 0, parent
);
739 const Item item
= data(ix
, ItemRole
).value
<Item
>();
740 bool isItem
= item
.isValid();
743 if ((*checkFunc
)(item
))
745 // For efficiency, emit a single signal for each group of
746 // consecutive items, rather than a separate signal for each item.
754 emit
dataChanged(index(start
, startColumn
, parent
), index(end
, endColumn
, parent
));
757 signalDataChanged(checkFunc
, startColumn
, endColumn
, ix
);
761 emit
dataChanged(index(start
, startColumn
, parent
), index(end
, endColumn
, parent
));
764 /******************************************************************************
765 * Signal every minute that the time-to-alarm values have changed.
767 static bool checkItem_isActive(const Item
& item
)
768 { return item
.mimeType() == KAlarm::MIME_ACTIVE
; }
770 void AkonadiModel::slotUpdateTimeTo()
772 signalDataChanged(&checkItem_isActive
, TimeToColumn
, TimeToColumn
, QModelIndex());
776 /******************************************************************************
777 * Called when the colour used to display archived alarms has changed.
779 static bool checkItem_isArchived(const Item
& item
)
780 { return item
.mimeType() == KAlarm::MIME_ARCHIVED
; }
782 void AkonadiModel::slotUpdateArchivedColour(const QColor
&)
785 signalDataChanged(&checkItem_isArchived
, 0, ColumnCount
- 1, QModelIndex());
788 /******************************************************************************
789 * Called when the colour used to display disabled alarms has changed.
791 static bool checkItem_isDisabled(const Item
& item
)
793 if (item
.hasPayload
<KAEvent
>())
795 KAEvent event
= item
.payload
<KAEvent
>();
797 return !event
.enabled();
802 void AkonadiModel::slotUpdateDisabledColour(const QColor
&)
805 signalDataChanged(&checkItem_isDisabled
, 0, ColumnCount
- 1, QModelIndex());
808 /******************************************************************************
809 * Called when the definition of holidays has changed.
810 * Update the next trigger time for all alarms which are set to recur only on
813 static bool checkItem_excludesHolidays(const Item
& item
)
815 if (item
.hasPayload
<KAEvent
>())
817 KAEvent event
= item
.payload
<KAEvent
>();
818 if (event
.isValid() && event
.holidaysExcluded())
820 event
.updateHolidays();
827 void AkonadiModel::slotUpdateHolidays()
830 Q_ASSERT(TimeToColumn
== TimeColumn
+ 1); // signal should be emitted only for TimeTo and Time columns
831 signalDataChanged(&checkItem_excludesHolidays
, TimeColumn
, TimeToColumn
, QModelIndex());
834 /******************************************************************************
835 * Called when the definition of working hours has changed.
836 * Update the next trigger time for all alarms which are set to recur only
837 * during working hours.
839 static bool checkItem_workTimeOnly(const Item
& item
)
841 if (item
.hasPayload
<KAEvent
>())
843 KAEvent event
= item
.payload
<KAEvent
>();
844 if (event
.isValid() && event
.workTimeOnly())
846 event
.updateWorkHours();
853 void AkonadiModel::slotUpdateWorkingHours()
856 Q_ASSERT(TimeToColumn
== TimeColumn
+ 1); // signal should be emitted only for TimeTo and Time columns
857 signalDataChanged(&checkItem_workTimeOnly
, TimeColumn
, TimeToColumn
, QModelIndex());
860 /******************************************************************************
861 * Called when the command error status of an alarm has changed, to save the new
862 * status and update the visual command error indication.
864 void AkonadiModel::updateCommandError(const KAEvent
& event
)
866 #warning ensure commandError is set when loading an alarm by Akonadi
867 QModelIndexList list
= match(QModelIndex(), ItemIdRole
, event
.itemId(), 1, Qt::MatchExactly
| Qt::MatchRecursive
);
869 setData(list
[0], QVariant(static_cast<int>(event
.commandError())), CommandErrorRole
);
872 /******************************************************************************
873 * Set the background color for displaying the collection and its alarms.
875 void AkonadiModel::setBackgroundColor(Collection
& collection
, const QColor
& colour
)
877 CollectionAttribute
* attr
= collection
.attribute
<CollectionAttribute
>(Entity::AddIfMissing
);
878 attr
->setBackgroundColor(colour
);
879 QModelIndex ix
= modelIndexForCollection(this, collection
);
881 signalDataChanged(&checkItem_true
, 0, ColumnCount
- 1, ix
);
884 /******************************************************************************
885 * Return the background color for displaying the collection and its alarms.
887 QColor
AkonadiModel::backgroundColor(const Akonadi::Collection
& collection
) const
889 if (!collection
.isValid() || !collection
.hasAttribute
<CollectionAttribute
>())
891 return collection
.attribute
<CollectionAttribute
>()->backgroundColor();
894 /******************************************************************************
895 * Return the display name for the collection.
897 QString
AkonadiModel::displayName(const Akonadi::Collection
& collection
) const
900 if (collection
.isValid() && collection
.hasAttribute
<EntityDisplayAttribute
>())
901 name
= collection
.attribute
<EntityDisplayAttribute
>()->displayName();
902 return name
.isEmpty() ? collection
.name() : name
;
905 /******************************************************************************
906 * Return the storage type (file, directory, etc.) for the collection.
908 QString
AkonadiModel::storageType(const Akonadi::Collection
& collection
) const
910 KUrl url
= collection
.remoteId();
911 return !url
.isLocalFile() ? i18nc("@info/plain", "URL")
912 : QFileInfo(url
.toLocalFile()).isDir() ? i18nc("@info/plain Directory in filesystem", "Directory")
913 : i18nc("@info/plain", "File");
916 /******************************************************************************
917 * Return the repetition text.
919 QString
AkonadiModel::repeatText(const KAEvent
& event
) const
921 QString repeatText
= event
.recurrenceText(true);
922 if (repeatText
.isEmpty())
923 repeatText
= event
.repetitionText(true);
927 /******************************************************************************
928 * Return a string for sorting the repetition column.
930 QString
AkonadiModel::repeatOrder(const KAEvent
& event
) const
933 int repeatInterval
= 0;
934 if (event
.repeatAtLogin())
938 repeatInterval
= event
.recurInterval();
939 switch (event
.recurType())
941 case KARecurrence::MINUTELY
:
944 case KARecurrence::DAILY
:
947 case KARecurrence::WEEKLY
:
950 case KARecurrence::MONTHLY_DAY
:
951 case KARecurrence::MONTHLY_POS
:
954 case KARecurrence::ANNUAL_DATE
:
955 case KARecurrence::ANNUAL_POS
:
958 case KARecurrence::NO_RECUR
:
963 return QString("%1%2").arg(static_cast<char>('0' + repeatOrder
)).arg(repeatInterval
, 8, 10, QLatin1Char('0'));
966 /******************************************************************************
967 * Return the icon associated with the event's action.
969 QPixmap
* AkonadiModel::eventIcon(const KAEvent
& event
) const
971 switch (event
.action())
979 case KAAlarm::COMMAND
:
980 if (!event
.commandDisplay())
982 // fall through to MESSAGE
983 case KAAlarm::MESSAGE
:
989 /******************************************************************************
990 * Returns the QWhatsThis text for a specified column.
992 QString
AkonadiModel::whatsThisText(int column
) const
997 return i18nc("@info:whatsthis", "Next scheduled date and time of the alarm");
999 return i18nc("@info:whatsthis", "How long until the next scheduled trigger of the alarm");
1001 return i18nc("@info:whatsthis", "How often the alarm recurs");
1003 return i18nc("@info:whatsthis", "Background color of alarm message");
1005 return i18nc("@info:whatsthis", "Alarm type (message, file, command or email)");
1007 return i18nc("@info:whatsthis", "Alarm message text, URL of text file to display, command to execute, or email subject line");
1008 case TemplateNameColumn
:
1009 return i18nc("@info:whatsthis", "Name of the alarm template");
1015 /******************************************************************************
1016 * Returns the index to a specified event.
1018 QModelIndex
AkonadiModel::eventIndex(const KAEvent
& event
)
1020 QModelIndexList list
= match(QModelIndex(), EntityTreeModel::ItemIdRole
, event
.itemId(), 1, Qt::MatchExactly
| Qt::MatchRecursive
);
1022 return QModelIndex();
1027 /******************************************************************************
1028 * Return all events of a given type belonging to a collection.
1030 KAEvent::List
AkonadiModel::events(Akonadi::Collection
& collection
, KAlarm::CalEvent::Type type
) const
1033 QModelIndex ix
= modelIndexForCollection(this, collection
);
1035 getChildEvents(ix
, type
, list
);
1039 /******************************************************************************
1040 * Recursive function to append all child Events with a given mime type.
1042 void AkonadiModel::getChildEvents(const QModelIndex
& parent
, KAlarm::CalEvent::Type type
, KAEvent::List
& events
) const
1044 for (int row
= 0, count
= rowCount(parent
); row
< count
; ++row
)
1046 const QModelIndex ix
= index(row
, 0, parent
);
1047 const Item item
= data(ix
, ItemRole
).value
<Item
>();
1050 if (item
.hasPayload
<KAEvent
>())
1052 KAEvent event
= item
.payload
<KAEvent
>();
1053 if (event
.isValid() && event
.category() == type
)
1055 if (item
.hasAttribute
<EventAttribute
>())
1057 KAEvent::CmdErrType err
= item
.attribute
<EventAttribute
>()->commandError();
1058 event
.setCommandError(err
, false);
1066 Collection c
= ix
.data(CollectionRole
).value
<Collection
>();
1068 getChildEvents(ix
, type
, events
);
1074 KAEvent
AkonadiModel::event(const QModelIndex
& index
) const
1076 const Item item
= index
.data(EntityTreeModel::ItemRole
).value
<Item
>();
1077 if (item
.isValid() && item
.hasPayload
<KAEvent
>())
1079 KAEvent event
= item
.payload
<KAEvent
>();
1080 if (event
.isValid() && item
.hasAttribute
<EventAttribute
>())
1082 KAEvent::CmdErrType err
= item
.attribute
<EventAttribute
>()->commandError();
1083 event
.setCommandError(err
);
1090 KAEvent
AkonadiModel::event(Item::Id itemId
) const
1092 QModelIndexList list
= match(QModelIndex(), EntityTreeModel::ItemIdRole
, itemId
, 1, Qt::MatchExactly
| Qt::MatchRecursive
);
1095 return event(list
.at(0));
1099 /******************************************************************************
1100 * Return all events in all calendars.
1102 QList
<KAEvent
*> AkonadiModel::events() const
1104 QList
<KAEvent
*> list
;
1105 Collection::List collections
= mMonitor
->collectionsMonitored();
1106 foreach (const Collection
& c
, collections
)
1112 /******************************************************************************
1113 * Add an event to the default or a user-selected Collection.
1115 AkonadiModel::Result
AkonadiModel::addEvent(KAEvent
* event
, KAlarm::CalEvent::Type type
, QWidget
* promptParent
, bool noPrompt
)
1117 kDebug() << event
->id();
1119 // Determine parent collection - prompt or use default
1121 Collection collection
= destination(type
, Collection::CanCreateItem
, promptParent
, noPrompt
, &cancelled
);
1122 if (!collection
.isValid())
1127 kDebug() << "No collection";
1130 if (!addEvent(event
, collection
))
1132 kDebug() << "Failed";
1133 return Failed
; // event was deleted by addEvent()
1139 /******************************************************************************
1140 * Add events to a specified Collection.
1141 * Events which are scheduled to be added to the collection are updated with
1142 * their Akonadi item ID.
1143 * The caller must connect to the itemDone() signal to check whether events
1144 * have been added successfully. Note that the first signal may be emitted
1145 * before this function returns.
1146 * Reply = true if item creation has been scheduled for all events,
1147 * false if at least one item creation failed to be scheduled.
1149 bool AkonadiModel::addEvents(const QList
<KAEvent
*>& events
, Collection
& collection
)
1152 for (int i
= 0, count
= events
.count(); i
< count
; ++i
)
1153 ok
= ok
&& addEvent(*events
[i
], collection
);
1157 /******************************************************************************
1158 * Add an event to a specified Collection.
1159 * If the event is scheduled to be added to the collection, it is updated with
1160 * its Akonadi item ID.
1161 * The event's 'updated' flag is cleared.
1162 * The caller must connect to the itemDone() signal to check whether events
1163 * have been added successfully.
1164 * Reply = true if item creation has been scheduled.
1166 bool AkonadiModel::addEvent(KAEvent
& event
, Collection
& collection
)
1169 if (!setItemPayload(item
, event
, collection
))
1171 event
.setItemId(item
.id());
1172 ItemCreateJob
* job
= new ItemCreateJob(item
, collection
);
1173 connect(job
, SIGNAL(result(KJob
*)), SLOT(itemJobDone(KJob
*)));
1174 mPendingItems
[job
] = item
.id();
1179 /******************************************************************************
1180 * Update an event in its collection.
1181 * The event retains its existing Akonadi item ID.
1182 * The event's 'updated' flag is cleared.
1183 * The caller must connect to the itemDone() signal to check whether the event
1184 * has been updated successfully.
1185 * Reply = true if item update has been scheduled.
1187 bool AkonadiModel::updateEvent(KAEvent
& event
)
1189 return updateEvent(event
.itemId(), event
);
1191 bool AkonadiModel::updateEvent(Akonadi::Entity::Id itemId
, KAEvent
& newEvent
)
1193 QModelIndexList list
= match(QModelIndex(), EntityTreeModel::ItemIdRole
, itemId
, 1, Qt::MatchExactly
| Qt::MatchRecursive
);
1196 Collection collection
= list
.at(0).data(EntityTreeModel::ParentCollectionRole
).value
<Collection
>();
1197 Item item
= list
.at(0).data(EntityTreeModel::ItemRole
).value
<Item
>();
1198 if (!setItemPayload(item
, newEvent
, collection
))
1200 ItemModifyJob
* job
= new ItemModifyJob(item
);
1201 connect(job
, SIGNAL(result(KJob
*)), SLOT(itemJobDone(KJob
*)));
1202 mPendingItems
[job
] = itemId
;
1205 #warning Ensure KAlarm event list is updated correctly before and after Akonadi update
1208 /******************************************************************************
1209 * Initialise an Item with an event.
1210 * Note that the event is not updated with the Item ID.
1211 * The event's 'updated' flag is cleared.
1213 bool AkonadiModel::setItemPayload(Item
& item
, KAEvent
& event
, const Collection
& collection
)
1216 switch (event
.category())
1218 case KAlarm::CalEvent::ACTIVE
: mimetype
= KAlarm::MIME_ACTIVE
; break;
1219 case KAlarm::CalEvent::ARCHIVED
: mimetype
= KAlarm::MIME_ARCHIVED
; break;
1220 case KAlarm::CalEvent::TEMPLATE
: mimetype
= KAlarm::MIME_TEMPLATE
; break;
1221 default: Q_ASSERT(0); return false;
1223 if (!collection
.contentMimeTypes().contains(mimetype
))
1225 kWarning() << "Invalid mime type for Collection";
1228 event
.clearUpdated();
1229 item
.setMimeType(mimetype
);
1230 item
.setPayload
<KAEvent
>(event
);
1234 /******************************************************************************
1235 * Delete an event from its collection.
1237 bool AkonadiModel::deleteEvent(const KAEvent
& event
)
1239 return deleteEvent(event
.itemId());
1241 bool AkonadiModel::deleteEvent(Akonadi::Entity::Id itemId
)
1243 QModelIndexList list
= match(QModelIndex(), EntityTreeModel::ItemIdRole
, itemId
, 1, Qt::MatchExactly
| Qt::MatchRecursive
);
1246 Item item
= list
.at(0).data(EntityTreeModel::ItemRole
).value
<Item
>();
1247 ItemDeleteJob
* job
= new ItemDeleteJob(item
);
1248 connect(job
, SIGNAL(result(KJob
*)), SLOT(itemJobDone(KJob
*)));
1249 mPendingItems
[job
] = itemId
;
1254 /******************************************************************************
1255 * Called when an item job has completed.
1256 * Checks for any error.
1258 void AkonadiModel::itemJobDone(KJob
* j
)
1260 QMap
<KJob
*, Entity::Id
>::iterator it
= mPendingItems
.find(j
);
1261 Entity::Id itemId
= -1;
1262 if (it
!= mPendingItems
.end())
1264 itemId
= it
.value();
1265 mPendingItems
.erase(it
);
1270 QByteArray jobClass
= j
->metaObject()->className();
1271 if (jobClass
== "Akonadi::ItemCreateJob")
1272 errMsg
= i18nc("@info/plain", "Failed to create alarm.");
1273 else if (jobClass
== "Akonadi::ItemModifyJob")
1274 errMsg
= i18nc("@info/plain", "Failed to update alarm.");
1275 else if (jobClass
== "Akonadi::ItemDeleteJob")
1276 errMsg
= i18nc("@info/plain", "Failed to delete alarm.");
1279 kError() << errMsg
<< itemId
<< ":" << j
->errorString();
1280 emit
itemDone(itemId
, false);
1281 KMessageBox::error(0, i18nc("@info", "%1<nl/>(%2)", errMsg
, j
->errorString()));
1282 #warning No widget parent
1285 emit
itemDone(itemId
);
1288 /******************************************************************************
1289 * Called when a collection job has completed.
1290 * Checks for any error.
1292 void AkonadiModel::collectionJobDone(KJob
* j
)
1294 Collection::Id id
= -1;
1298 QByteArray jobClass
= j
->metaObject()->className();
1299 if (jobClass
== "Akonadi::CollectionModifyJob")
1301 Collection c
= static_cast<CollectionModifyJob
*>(j
)->collection();
1303 errMsg
= i18nc("@info", "Could not update calendar <resource>%1</resource>.", displayName(c
));
1307 kError() << errMsg
<< ":" << j
->errorString();
1308 // emit collectionDone(id, false);
1309 KMessageBox::error(0, i18nc("@info", "%1<nl/>(%2)", errMsg
, j
->errorString()));
1310 #warning No widget parent
1314 /******************************************************************************
1315 * Called when rows have been inserted into the model.
1317 void AkonadiModel::slotRowsInserted(const QModelIndex
& parent
, int start
, int end
)
1319 for (int row
= start
; row
<= end
; ++row
)
1321 const QModelIndex ix
= index(row
, 0, parent
);
1322 const Collection collection
= ix
.data(CollectionRole
).value
<Collection
>();
1323 if (collection
.isValid())
1325 QSet
<QByteArray
> attrs
;
1326 attrs
+= CollectionAttribute::name();
1327 slotCollectionChanged(collection
, attrs
);
1330 EventList events
= eventList(parent
, start
, end
);
1331 if (!events
.isEmpty())
1332 emit
eventsAdded(events
);
1335 /******************************************************************************
1336 * Called when rows are about to be removed from the model.
1338 void AkonadiModel::slotRowsAboutToBeRemoved(const QModelIndex
& parent
, int start
, int end
)
1340 EventList events
= eventList(parent
, start
, end
);
1341 if (!events
.isEmpty())
1342 emit
eventsToBeRemoved(events
);
1345 /******************************************************************************
1346 * Return a list of KAEvent/Collection pairs for a given range of rows.
1348 AkonadiModel::EventList
AkonadiModel::eventList(const QModelIndex
& parent
, int start
, int end
)
1351 for (int row
= start
; row
<= end
; ++row
)
1353 QModelIndex ix
= index(row
, 0, parent
);
1354 const Item item
= ix
.data(ItemRole
).value
<Item
>();
1355 kDebug()<<"row="<<row
<<", item valid="<<item
.isValid()<<", has payload="<<item
.hasPayload
<KAEvent
>();
1356 if (item
.isValid() && item
.hasPayload
<KAEvent
>())
1358 KAEvent event
= item
.payload
<KAEvent
>();
1359 if (event
.isValid())
1360 events
+= Event(event
, data(ix
, ParentCollectionRole
).value
<Collection
>());
1366 /******************************************************************************
1367 * Called when a monitored collection's properties or content have changed.
1368 * Emits a signal if the writable property has changed.
1370 void AkonadiModel::slotCollectionChanged(const Collection
& collection
, const QSet
<QByteArray
>& attributeNames
)
1373 #warning Ensure collection rights is initialised at startup
1374 Collection::Rights oldRights
= mCollectionRights
.value(collection
.id(), Collection::AllRights
);
1375 Collection::Rights newRights
= collection
.rights() & writableRights
;
1376 if (newRights
!= oldRights
)
1378 kDebug()<<"rights changed";
1379 mCollectionRights
[collection
.id()] = newRights
;
1380 emit
collectionStatusChanged(collection
, ReadOnly
, (newRights
!= writableRights
));
1383 if (attributeNames
.contains(CollectionAttribute::name()))
1385 static bool first
= true;
1386 bool oldEnabled
= mCollectionEnabled
.value(collection
.id(), false);
1387 bool newEnabled
= collection
.hasAttribute
<CollectionAttribute
>() ? collection
.attribute
<CollectionAttribute
>()->isEnabled() : false;
1388 if (first
|| newEnabled
!= oldEnabled
)
1390 kDebug()<<"enabled changed ->"<<newEnabled
;
1392 mCollectionEnabled
[collection
.id()] = newEnabled
;
1393 emit
collectionStatusChanged(collection
, Enabled
, newEnabled
);
1398 /******************************************************************************
1399 * Called when a monitored collection is removed.
1401 void AkonadiModel::slotCollectionRemoved(const Collection
& collection
)
1403 kDebug() << collection
.id();
1404 mCollectionRights
.remove(collection
.id());
1407 /******************************************************************************
1408 * Called when an item in the monitored collections has changed.
1410 void AkonadiModel::slotMonitoredItemChanged(const Akonadi::Item
& item
, const QSet
<QByteArray
>&)
1412 if (!item
.isValid() || !item
.hasPayload
<KAEvent
>())
1414 KAEvent event
= item
.payload
<KAEvent
>();
1415 if (!event
.isValid())
1417 const QModelIndexList indexes
= match(QModelIndex(), ItemIdRole
, item
.id(), 1, Qt::MatchExactly
| Qt::MatchRecursive
);
1418 foreach (const QModelIndex
& index
, indexes
)
1420 if (index
.isValid())
1422 // Wait to ensure that the base EntityTreeModel has processed the
1423 // itemChanged() signal first, before we emit eventChanged().
1424 mPendingEventChanges
.enqueue(Event(event
, data(index
, ParentCollectionRole
).value
<Collection
>()));
1425 QTimer::singleShot(0, this, SLOT(slotEmitEventChanged()));
1431 /******************************************************************************
1432 * Called to emit a signal when an event in the monitored collections has
1435 void AkonadiModel::slotEmitEventChanged()
1437 while (!mPendingEventChanges
.isEmpty())
1439 emit
eventChanged(mPendingEventChanges
.dequeue());
1443 /******************************************************************************
1444 * Find the QModelIndex of a collection.
1446 QModelIndex
AkonadiModel::collectionIndex(const Collection
& collection
) const
1448 QModelIndex ix
= modelIndexForCollection(this, collection
);
1450 return QModelIndex();
1454 /******************************************************************************
1455 * Find the collection with the specified Akonadi ID.
1457 Collection
AkonadiModel::collectionById(Collection::Id id
) const
1459 QModelIndex ix
= modelIndexForCollection(this, Collection(id
));
1461 return Collection();
1462 return ix
.data(EntityTreeModel::CollectionRole
).value
<Collection
>();
1465 /******************************************************************************
1466 * Find the collection containing the specified Akonadi item ID.
1468 Collection
AkonadiModel::collectionForItem(Item::Id id
) const
1470 QModelIndexList list
= match(QModelIndex(), ItemIdRole
, id
, 1, Qt::MatchExactly
| Qt::MatchRecursive
);
1472 return Collection();
1473 return list
[0].data(EntityTreeModel::CollectionRole
).value
<Collection
>();
1476 KAlarm::CalEvent::Types
AkonadiModel::types(const Collection
& collection
)
1478 KAlarm::CalEvent::Types types
= 0;
1479 QStringList mimeTypes
= collection
.contentMimeTypes();
1480 if (mimeTypes
.contains(KAlarm::MIME_ACTIVE
))
1481 types
|= KAlarm::CalEvent::ACTIVE
;
1482 if (mimeTypes
.contains(KAlarm::MIME_ARCHIVED
))
1483 types
|= KAlarm::CalEvent::ARCHIVED
;
1484 if (mimeTypes
.contains(KAlarm::MIME_TEMPLATE
))
1485 types
|= KAlarm::CalEvent::TEMPLATE
;
1489 /******************************************************************************
1490 * Check whether the alarm types in a calendar correspond with a collection's
1492 * Reply = true if at least 1 alarm is the right type.
1494 bool AkonadiModel::checkAlarmTypes(const Akonadi::Collection
& collection
, KCal::CalendarLocal
& calendar
)
1496 KAlarm::CalEvent::Types etypes
= types(collection
);
1501 const KCal::Event::List events
= calendar
.rawEvents();
1502 for (int i
= 0, iend
= events
.count(); i
< iend
; ++i
)
1504 KAlarm::CalEvent::Type s
= KAlarm::CalEvent::status(events
[i
]);
1513 return false; // contains only wrong alarm types
1519 /*=============================================================================
1520 = Class: CollectionMimeTypeFilterModel
1521 = Proxy model to restrict its contents to Collections, not Items, containing
1522 = specified content mime types.
1523 =============================================================================*/
1524 class CollectionMimeTypeFilterModel
: public Akonadi::EntityMimeTypeFilterModel
1528 explicit CollectionMimeTypeFilterModel(QObject
* parent
= 0);
1529 void setEventTypeFilter(KAlarm::CalEvent::Type
);
1530 void setFilterWritable(bool writable
);
1531 Akonadi::Collection
collection(int row
) const;
1532 Akonadi::Collection
collection(const QModelIndex
&) const;
1535 virtual bool filterAcceptsRow(int sourceRow
, const QModelIndex
& sourceParent
) const;
1538 QString mMimeType
; // collection content type contained in this model
1539 bool mWritableOnly
; // only include writable collections in this model
1542 CollectionMimeTypeFilterModel::CollectionMimeTypeFilterModel(QObject
* parent
)
1543 : EntityMimeTypeFilterModel(parent
),
1545 mWritableOnly(false)
1547 addMimeTypeInclusionFilter(Collection::mimeType());
1548 setHeaderGroup(EntityTreeModel::CollectionTreeHeaders
);
1549 setSourceModel(AkonadiModel::instance());
1552 void CollectionMimeTypeFilterModel::setEventTypeFilter(KAlarm::CalEvent::Type type
)
1554 QString mimeType
= KAlarm::CalEvent::mimeType(type
);
1555 if (mimeType
!= mMimeType
)
1557 mMimeType
= mimeType
;
1562 void CollectionMimeTypeFilterModel::setFilterWritable(bool writable
)
1564 if (writable
!= mWritableOnly
)
1566 mWritableOnly
= writable
;
1571 bool CollectionMimeTypeFilterModel::filterAcceptsRow(int sourceRow
, const QModelIndex
& sourceParent
) const
1573 if (!EntityMimeTypeFilterModel::filterAcceptsRow(sourceRow
, sourceParent
))
1575 if (!mWritableOnly
&& mMimeType
.isEmpty())
1577 AkonadiModel
* model
= AkonadiModel::instance();
1578 QModelIndex ix
= model
->index(sourceRow
, 0, sourceParent
);
1579 Collection collection
= model
->data(ix
, AkonadiModel::CollectionRole
).value
<Collection
>();
1580 if (mWritableOnly
&& collection
.rights() == Collection::ReadOnly
)
1582 return mMimeType
.isEmpty() || collection
.contentMimeTypes().contains(mMimeType
);
1585 /******************************************************************************
1586 * Return the collection for a given row.
1588 Collection
CollectionMimeTypeFilterModel::collection(int row
) const
1590 return static_cast<AkonadiModel
*>(sourceModel())->data(mapToSource(index(row
, 0)), EntityTreeModel::CollectionRole
).value
<Collection
>();
1593 Collection
CollectionMimeTypeFilterModel::collection(const QModelIndex
& index
) const
1595 return static_cast<AkonadiModel
*>(sourceModel())->data(mapToSource(index
), EntityTreeModel::CollectionRole
).value
<Collection
>();
1599 /*=============================================================================
1600 = Class: CollectionListModel
1601 = Proxy model converting the collection tree into a flat list.
1602 = The model may be restricted to specified content mime types.
1603 =============================================================================*/
1605 CollectionListModel::CollectionListModel(QObject
* parent
)
1606 : KDescendantsProxyModel(parent
)
1608 setSourceModel(new CollectionMimeTypeFilterModel(this));
1609 setDisplayAncestorData(false);
1612 /******************************************************************************
1613 * Return the collection for a given row.
1615 Collection
CollectionListModel::collection(int row
) const
1617 // return AkonadiModel::instance()->data(mapToSource(index(row, 0)), EntityTreeModel::CollectionRole).value<Collection>();
1618 return data(index(row
, 0), EntityTreeModel::CollectionRole
).value
<Collection
>();
1621 Collection
CollectionListModel::collection(const QModelIndex
& index
) const
1623 // return AkonadiModel::instance()->data(mapToSource(index), EntityTreeModel::CollectionRole).value<Collection>();
1624 return data(index
, EntityTreeModel::CollectionRole
).value
<Collection
>();
1627 void CollectionListModel::setEventTypeFilter(KAlarm::CalEvent::Type type
)
1629 static_cast<CollectionMimeTypeFilterModel
*>(sourceModel())->setEventTypeFilter(type
);
1632 void CollectionListModel::setFilterWritable(bool writable
)
1634 static_cast<CollectionMimeTypeFilterModel
*>(sourceModel())->setFilterWritable(writable
);
1637 bool CollectionListModel::isDescendantOf(const QModelIndex
& ancestor
, const QModelIndex
& descendant
) const
1639 Q_UNUSED(descendant
);
1640 return !ancestor
.isValid();
1644 /*=============================================================================
1645 = Class: CollectionCheckListModel
1646 = Proxy model providing a checkable collection list.
1647 =============================================================================*/
1649 CollectionCheckListModel
* CollectionCheckListModel::mInstance
= 0;
1651 CollectionCheckListModel
* CollectionCheckListModel::instance()
1654 mInstance
= new CollectionCheckListModel(qApp
);
1658 CollectionCheckListModel::CollectionCheckListModel(QObject
* parent
)
1659 : CheckableItemProxyModel(parent
)
1661 CollectionListModel
* model
= new CollectionListModel(this);
1662 setSourceModel(model
);
1663 mSelectionModel
= new QItemSelectionModel(model
);
1664 setSelectionModel(mSelectionModel
);
1665 connect(mSelectionModel
, SIGNAL(selectionChanged(const QItemSelection
&, const QItemSelection
&)),
1666 SLOT(selectionChanged(const QItemSelection
&, const QItemSelection
&)));
1667 connect(model
, SIGNAL(rowsInserted(const QModelIndex
&, int, int)), SLOT(slotRowsInserted(const QModelIndex
&, int, int)));
1670 /******************************************************************************
1671 * Return the collection for a given row.
1673 Collection
CollectionCheckListModel::collection(int row
) const
1675 return static_cast<CollectionListModel
*>(sourceModel())->collection(mapToSource(index(row
, 0)));
1678 Collection
CollectionCheckListModel::collection(const QModelIndex
& index
) const
1680 return static_cast<CollectionListModel
*>(sourceModel())->collection(mapToSource(index
));
1683 /******************************************************************************
1684 * Called when rows have been inserted into the model.
1685 * Select or deselect them according to their enabled status.
1687 void CollectionCheckListModel::slotRowsInserted(const QModelIndex
& parent
, int start
, int end
)
1689 CollectionListModel
* model
= static_cast<CollectionListModel
*>(sourceModel());
1690 for (int row
= start
; row
<= end
; ++row
)
1692 const QModelIndex ix
= mapToSource(index(row
, 0, parent
));
1693 const Collection collection
= model
->collection(ix
);
1694 if (collection
.isValid())
1696 QItemSelectionModel::SelectionFlags sel
= (collection
.hasAttribute
<CollectionAttribute
>()
1697 && collection
.attribute
<CollectionAttribute
>()->isEnabled())
1698 ? QItemSelectionModel::Select
: QItemSelectionModel::Deselect
;
1699 mSelectionModel
->select(ix
, sel
);
1704 /******************************************************************************
1705 * Called when the user has ticked/unticked a collection to enable/disable it.
1707 void CollectionCheckListModel::selectionChanged(const QItemSelection
& selected
, const QItemSelection
& deselected
)
1709 const QModelIndexList sel
= selected
.indexes();
1710 foreach (const QModelIndex
& ix
, sel
)
1712 CollectionControlModel::setEnabled(static_cast<CollectionListModel
*>(sourceModel())->collection(ix
), true);
1713 kDebug()<<"Enabled";
1715 const QModelIndexList desel
= deselected
.indexes();
1716 foreach (const QModelIndex
& ix
, desel
)
1718 // The collection is to be disabled.
1719 // Check for eligibility.
1720 const Collection collection
= static_cast<CollectionListModel
*>(sourceModel())->collection(ix
);
1721 if (!collection
.isValid() || !collection
.hasAttribute
<CollectionAttribute
>())
1722 {kDebug()<<"No attribute";
1725 const CollectionAttribute
* attr
= collection
.attribute
<CollectionAttribute
>();
1726 if (!attr
->isEnabled())
1727 {kDebug()<<"Already disabled";
1730 if (attr
->standard() != KAlarm::CalEvent::EMPTY
)
1732 // It's the standard collection for some alarm type.
1733 if (attr
->isStandard(KAlarm::CalEvent::ACTIVE
))
1735 KMessageBox::sorry(static_cast<QWidget
*>(parent()),
1736 i18nc("@info", "You cannot disable your default active alarm calendar."));
1739 if (attr
->isStandard(KAlarm::CalEvent::ARCHIVED
) && Preferences::archivedKeepDays())
1741 // Only allow the archived alarms standard collection to be disabled if
1742 // we're not saving expired alarms.
1743 KMessageBox::sorry(static_cast<QWidget
*>(parent()),
1744 i18nc("@info", "You cannot disable your default archived alarm calendar "
1745 "while expired alarms are configured to be kept."));
1748 if (KMessageBox::warningContinueCancel(static_cast<QWidget
*>(parent()),
1749 i18nc("@info", "Do you really want to disable your default calendar?"))
1750 == KMessageBox::Cancel
)
1753 CollectionControlModel::setEnabled(collection
, false);
1754 kDebug()<<"Disabled";
1759 /*=============================================================================
1760 = Class: CollectionFilterCheckListModel
1761 = Proxy model providing a checkable collection list, filtered by mime type.
1762 =============================================================================*/
1763 CollectionFilterCheckListModel::CollectionFilterCheckListModel(QObject
* parent
)
1764 : QSortFilterProxyModel(parent
),
1767 setSourceModel(CollectionCheckListModel::instance());
1770 void CollectionFilterCheckListModel::setEventTypeFilter(KAlarm::CalEvent::Type type
)
1772 QString mimeType
= KAlarm::CalEvent::mimeType(type
);
1773 if (mimeType
!= mMimeType
)
1775 mMimeType
= mimeType
;
1780 /******************************************************************************
1781 * Return the collection for a given row.
1783 Collection
CollectionFilterCheckListModel::collection(int row
) const
1785 return CollectionCheckListModel::instance()->collection(mapToSource(index(row
, 0)));
1788 Collection
CollectionFilterCheckListModel::collection(const QModelIndex
& index
) const
1790 return CollectionCheckListModel::instance()->collection(mapToSource(index
));
1793 bool CollectionFilterCheckListModel::filterAcceptsRow(int sourceRow
, const QModelIndex
& sourceParent
) const
1795 if (mMimeType
.isEmpty())
1797 CollectionCheckListModel
* model
= CollectionCheckListModel::instance();
1798 const Collection collection
= model
->collection(model
->index(sourceRow
, 0, sourceParent
));
1799 return collection
.contentMimeTypes().contains(mMimeType
);
1803 /*=============================================================================
1804 = Class: CollectionView
1805 = View displaying a list of collections.
1806 =============================================================================*/
1807 CollectionView::CollectionView(CollectionFilterCheckListModel
* model
, QWidget
* parent
)
1813 void CollectionView::setModel(QAbstractItemModel
* model
)
1815 model
->setData(QModelIndex(), viewOptions().font
, Qt::FontRole
);
1816 QListView::setModel(model
);
1819 /******************************************************************************
1820 * Return the collection for a given row.
1822 Collection
CollectionView::collection(int row
) const
1824 return static_cast<CollectionFilterCheckListModel
*>(model())->collection(row
);
1827 Collection
CollectionView::collection(const QModelIndex
& index
) const
1829 return static_cast<CollectionFilterCheckListModel
*>(model())->collection(index
);
1832 /******************************************************************************
1833 * Called when a mouse button is released.
1834 * Any currently selected collection is deselected.
1836 void CollectionView::mouseReleaseEvent(QMouseEvent
* e
)
1838 if (!indexAt(e
->pos()).isValid())
1840 QListView::mouseReleaseEvent(e
);
1843 /******************************************************************************
1844 * Called when a ToolTip or WhatsThis event occurs.
1846 bool CollectionView::viewportEvent(QEvent
* e
)
1848 if (e
->type() == QEvent::ToolTip
&& isActiveWindow())
1850 QHelpEvent
* he
= static_cast<QHelpEvent
*>(e
);
1851 QModelIndex index
= indexAt(he
->pos());
1852 QVariant value
= model()->data(index
, Qt::ToolTipRole
);
1853 if (qVariantCanConvert
<QString
>(value
))
1855 QString toolTip
= value
.toString();
1856 int i
= toolTip
.indexOf('@');
1859 int j
= toolTip
.indexOf(QRegExp("<(nl|br)", Qt::CaseInsensitive
), i
+ 1);
1860 int k
= toolTip
.indexOf('@', j
);
1861 QString name
= toolTip
.mid(i
+ 1, j
- i
- 1);
1862 value
= model()->data(index
, Qt::FontRole
);
1863 QFontMetrics
fm(qvariant_cast
<QFont
>(value
).resolve(viewOptions().font
));
1864 int textWidth
= fm
.boundingRect(name
).width() + 1;
1865 const int margin
= QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin
) + 1;
1866 QStyleOptionButton opt
;
1867 opt
.QStyleOption::operator=(viewOptions());
1868 opt
.rect
= rectForIndex(index
);
1869 int checkWidth
= QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator
, &opt
).width();
1870 int left
= spacing() + 3*margin
+ checkWidth
+ viewOptions().decorationSize
.width(); // left offset of text
1871 int right
= left
+ textWidth
;
1872 if (left
>= horizontalOffset() + spacing()
1873 && right
<= horizontalOffset() + width() - spacing() - 2*frameWidth())
1875 // The whole of the collection name is already displayed,
1876 // so omit it from the tooltip.
1878 toolTip
.remove(i
, k
+ 1 - i
);
1882 toolTip
.remove(k
, 1);
1883 toolTip
.remove(i
, 1);
1886 QToolTip::showText(he
->globalPos(), toolTip
, this);
1890 return QListView::viewportEvent(e
);
1894 /*=============================================================================
1895 = Class: CollectionControlModel
1896 = Proxy model to select which Collections will be enabled. Disabled Collections
1897 = are not populated or monitored; their contents are ignored. The set of
1898 = enabled Collections is stored in the config file's "Collections" group.
1899 = Note that this model is not used directly for displaying - its purpose is to
1900 = allow collections to be disabled, which will remove them from the other
1901 = collection models.
1902 =============================================================================*/
1904 CollectionControlModel
* CollectionControlModel::mInstance
= 0;
1905 bool CollectionControlModel::mAskDestination
= false;
1907 CollectionControlModel
* CollectionControlModel::instance()
1910 mInstance
= new CollectionControlModel(qApp
);
1914 CollectionControlModel::CollectionControlModel(QObject
* parent
)
1915 : FavoriteCollectionsModel(AkonadiModel::instance(), KConfigGroup(KGlobal::config(), "Collections"), parent
)
1917 // Initialise the list of enabled collections
1918 EntityMimeTypeFilterModel
* filter
= new EntityMimeTypeFilterModel(this);
1919 filter
->addMimeTypeInclusionFilter(Collection::mimeType());
1920 filter
->setSourceModel(AkonadiModel::instance());
1921 Collection::List collections
;
1922 findEnabledCollections(filter
, QModelIndex(), collections
);
1923 setCollections(collections
);
1925 connect(AkonadiModel::instance(), SIGNAL(collectionStatusChanged(const Akonadi::Collection
&, AkonadiModel::Change
, bool)),
1926 SLOT(statusChanged(const Akonadi::Collection
&, AkonadiModel::Change
, bool)));
1929 /******************************************************************************
1930 * Recursive function to check all collections' enabled status.
1932 void CollectionControlModel::findEnabledCollections(const EntityMimeTypeFilterModel
* filter
, const QModelIndex
& parent
, Collection::List
& collections
) const
1934 AkonadiModel
* model
= AkonadiModel::instance();
1935 for (int row
= 0, count
= filter
->rowCount(parent
); row
< count
; ++row
)
1937 const QModelIndex ix
= filter
->index(row
, 0, parent
);
1938 const Collection collection
= model
->data(filter
->mapToSource(ix
), AkonadiModel::CollectionRole
).value
<Collection
>();
1939 if (collection
.hasAttribute
<CollectionAttribute
>()
1940 && collection
.attribute
<CollectionAttribute
>()->isEnabled())
1941 collections
+= collection
;
1942 if (filter
->rowCount(ix
) > 0)
1943 findEnabledCollections(filter
, ix
, collections
);
1947 bool CollectionControlModel::isEnabled(const Collection
& collection
)
1949 return collection
.isValid() && instance()->collections().contains(collection
);
1952 void CollectionControlModel::setEnabled(const Collection
& collection
, bool enabled
)
1954 instance()->statusChanged(collection
, AkonadiModel::Enabled
, enabled
);
1957 void CollectionControlModel::statusChanged(const Collection
& collection
, AkonadiModel::Change change
, bool value
)
1959 if (change
== AkonadiModel::Enabled
)
1961 if (collection
.isValid())
1965 const Collection::List cols
= collections();
1966 foreach (const Collection
& c
, cols
)
1968 if (c
.id() == collection
.id())
1971 addCollection(collection
);
1974 removeCollection(collection
);
1975 AkonadiModel
* model
= static_cast<AkonadiModel
*>(sourceModel());
1976 model
->setData(model
->collectionIndex(collection
), value
, AkonadiModel::EnabledRole
);
1979 QModelIndex ix
= modelIndexForCollection(this, collection
);
1981 selectionModel()->select(mapFromSource(ix
), (enabled
? QItemSelectionModel::Select
: QItemSelectionModel::Deselect
));
1986 /******************************************************************************
1987 * Return whether a collection is both enabled and fully writable.
1989 bool CollectionControlModel::isWritable(const Akonadi::Collection
& collection
)
1991 if (!collection
.hasAttribute
<CollectionAttribute
>()
1992 || collection
.attribute
<CollectionAttribute
>()->compatibility() != KAlarm::Calendar::Current
)
1994 return isEnabled(collection
) && (collection
.rights() & writableRights
) == writableRights
;
1997 /******************************************************************************
1998 * Return the standard collection for a specified mime type.
2000 Collection
CollectionControlModel::getStandard(KAlarm::CalEvent::Type type
)
2002 QString mimeType
= KAlarm::CalEvent::mimeType(type
);
2003 Collection::List cols
= instance()->collections();
2004 for (int i
= 0, count
= cols
.count(); i
< count
; ++i
)
2006 if (cols
[i
].contentMimeTypes().contains(mimeType
)
2007 && cols
[i
].hasAttribute
<CollectionAttribute
>()
2008 && (cols
[i
].attribute
<CollectionAttribute
>()->standard() & type
))
2011 return Collection();
2014 /******************************************************************************
2015 * Return whether a collection is the standard collection for a specified
2018 bool CollectionControlModel::isStandard(Akonadi::Collection
& collection
, KAlarm::CalEvent::Type type
)
2020 if (!instance()->collections().contains(collection
))
2022 if (!collection
.hasAttribute
<CollectionAttribute
>())
2024 return collection
.attribute
<CollectionAttribute
>()->isStandard(type
);
2027 /******************************************************************************
2028 * Return the alarm type(s) for which a collection is the standard collection.
2030 KAlarm::CalEvent::Types
CollectionControlModel::standardTypes(const Collection
& collection
)
2032 if (!instance()->collections().contains(collection
))
2033 return KAlarm::CalEvent::EMPTY
;
2034 if (!collection
.hasAttribute
<CollectionAttribute
>())
2035 return KAlarm::CalEvent::EMPTY
;
2036 return collection
.attribute
<CollectionAttribute
>()->standard();
2039 /******************************************************************************
2040 * Set or clear a collection as the standard collection for a specified mime
2041 * type. If it is being set as standard, the standard status for the mime type
2042 * is cleared for all other collections.
2044 void CollectionControlModel::setStandard(Akonadi::Collection
& collection
, KAlarm::CalEvent::Type type
, bool standard
)
2046 AkonadiModel
* model
= AkonadiModel::instance();
2049 // The collection is being set as standard.
2050 // Clear the 'standard' status for all other collections.
2051 Collection::List cols
= instance()->collections();
2052 if (!cols
.contains(collection
))
2054 KAlarm::CalEvent::Types ctypes
= collection
.hasAttribute
<CollectionAttribute
>()
2055 ? collection
.attribute
<CollectionAttribute
>()->standard() : KAlarm::CalEvent::EMPTY
;
2057 return; // it's already the standard collection for this type
2058 for (int i
= 0, count
= cols
.count(); i
< count
; ++i
)
2060 KAlarm::CalEvent::Types types
;
2061 if (cols
[i
] == collection
)
2063 types
= ctypes
| type
;
2067 types
= cols
[i
].hasAttribute
<CollectionAttribute
>()
2068 ? cols
[i
].attribute
<CollectionAttribute
>()->standard() : KAlarm::CalEvent::EMPTY
;
2069 if (!(types
& type
))
2073 QModelIndex index
= model
->collectionIndex(cols
[i
]);
2074 model
->setData(index
, static_cast<int>(types
), AkonadiModel::IsStandardRole
);
2079 // The 'standard' status is being cleared for the collection.
2080 // The collection doesn't have to be in this model's list of collections.
2081 KAlarm::CalEvent::Types types
= collection
.hasAttribute
<CollectionAttribute
>()
2082 ? collection
.attribute
<CollectionAttribute
>()->standard() : KAlarm::CalEvent::EMPTY
;
2086 QModelIndex index
= model
->collectionIndex(collection
);
2087 model
->setData(index
, static_cast<int>(types
), AkonadiModel::IsStandardRole
);
2092 /******************************************************************************
2093 * Set which mime types a collection is the standard collection for.
2094 * If it is being set as standard for any mime types, the standard status for
2095 * those mime types is cleared for all other collections.
2097 void CollectionControlModel::setStandard(Akonadi::Collection
& collection
, KAlarm::CalEvent::Types types
)
2099 AkonadiModel
* model
= AkonadiModel::instance();
2102 // The collection is being set as standard for at least one mime type.
2103 // Clear the 'standard' status for all other collections.
2104 Collection::List cols
= instance()->collections();
2105 if (!cols
.contains(collection
))
2107 KAlarm::CalEvent::Types t
= collection
.hasAttribute
<CollectionAttribute
>()
2108 ? collection
.attribute
<CollectionAttribute
>()->standard() : KAlarm::CalEvent::EMPTY
;
2110 return; // there's no change to the collection's status
2111 for (int i
= 0, count
= cols
.count(); i
< count
; ++i
)
2113 KAlarm::CalEvent::Types t
;
2114 if (cols
[i
] == collection
)
2120 t
= cols
[i
].hasAttribute
<CollectionAttribute
>()
2121 ? cols
[i
].attribute
<CollectionAttribute
>()->standard() : KAlarm::CalEvent::EMPTY
;
2126 QModelIndex index
= model
->collectionIndex(cols
[i
]);
2127 model
->setData(index
, static_cast<int>(t
), AkonadiModel::IsStandardRole
);
2132 // The 'standard' status is being cleared for the collection.
2133 // The collection doesn't have to be in this model's list of collections.
2134 if (collection
.hasAttribute
<CollectionAttribute
>()
2135 && collection
.attribute
<CollectionAttribute
>()->standard())
2137 QModelIndex index
= model
->collectionIndex(collection
);
2138 model
->setData(index
, static_cast<int>(types
), AkonadiModel::IsStandardRole
);
2143 /******************************************************************************
2144 * Get the collection to use for storing an alarm.
2145 * Optionally, the standard collection for the alarm type is returned. If more
2146 * than one collection is a candidate, the user is prompted.
2148 Collection
CollectionControlModel::destination(KAlarm::CalEvent::Type type
, QWidget
* promptParent
, bool noPrompt
, bool* cancelled
)
2152 Collection standard
;
2153 if (type
== KAlarm::CalEvent::EMPTY
)
2155 standard
= getStandard(type
);
2156 // Archived alarms are always saved in the default resource,
2157 // else only prompt if necessary.
2158 if (type
== KAlarm::CalEvent::ARCHIVED
|| noPrompt
|| (!mAskDestination
&& standard
.isValid()))
2161 // Prompt for which collection to use
2162 CollectionListModel
* model
= new CollectionListModel(promptParent
);
2163 model
->setFilterWritable(true);
2164 model
->setEventTypeFilter(type
);
2166 switch (model
->rowCount())
2171 col
= model
->collection(0);
2175 // Use AutoQPointer to guard against crash on application exit while
2176 // the dialogue is still open. It prevents double deletion (both on
2177 // deletion of ResourceSelector, and on return from this function).
2178 AutoQPointer
<CollectionDialog
> dlg
= new CollectionDialog(model
, promptParent
);
2179 dlg
->setCaption(i18nc("@title:window", "Choose Calendar"));
2180 dlg
->setDefaultCollection(standard
);
2182 col
= dlg
->selectedCollection();
2183 if (!col
.isValid() && cancelled
)
2190 /******************************************************************************
2191 * Return the enabled collections which contain a specified mime type.
2192 * If 'writable' is true, only writable collections are included.
2194 Collection::List
CollectionControlModel::enabledCollections(KAlarm::CalEvent::Type type
, bool writable
)
2196 QString mimeType
= KAlarm::CalEvent::mimeType(type
);
2197 Collection::List cols
= instance()->collections();
2198 Collection::List result
;
2199 for (int i
= 0, count
= cols
.count(); i
< count
; ++i
)
2201 if (cols
[i
].contentMimeTypes().contains(mimeType
)
2202 && (!writable
|| ((cols
[i
].rights() & writableRights
) == writableRights
)))
2208 /******************************************************************************
2209 * Return the data for a given role, for a specified item.
2211 QVariant
CollectionControlModel::data(const QModelIndex
& index
, int role
) const
2213 return sourceModel()->data(mapToSource(index
), role
);
2217 /*=============================================================================
2218 = Class: ItemListModel
2219 = Filter proxy model containing all items (alarms/templates) of specified mime
2220 = types in enabled collections.
2221 =============================================================================*/
2222 ItemListModel::ItemListModel(KAlarm::CalEvent::Types allowed
, QObject
* parent
)
2223 : EntityMimeTypeFilterModel(parent
),
2224 mAllowedTypes(allowed
)
2226 KSelectionProxyModel
* selectionModel
= new KSelectionProxyModel(CollectionControlModel::instance()->selectionModel(), this);
2227 selectionModel
->setSourceModel(AkonadiModel::instance());
2228 selectionModel
->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection
);
2229 setSourceModel(selectionModel
);
2231 addMimeTypeExclusionFilter(Collection::mimeType());
2232 setHeaderGroup(EntityTreeModel::ItemListHeaders
);
2235 QStringList mimeTypes
= KAlarm::CalEvent::mimeTypes(allowed
);
2236 foreach (const QString
& mime
, mimeTypes
)
2237 addMimeTypeInclusionFilter(mime
);
2239 setHeaderGroup(EntityTreeModel::ItemListHeaders
);
2240 setSortRole(AkonadiModel::SortRole
);
2241 setDynamicSortFilter(true);
2242 #warning SLOT not defined
2243 // connect(this, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(rowsInsertedRemoved()));
2244 // connect(this, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SLOT(rowsInsertedRemoved()));
2247 int ItemListModel::columnCount(const QModelIndex
& parent
) const
2250 if (parent
.isValid())
2253 return AkonadiModel::ColumnCount
;
2256 /******************************************************************************
2257 * Called when rows have been inserted into the model.
2259 void ItemListModel::rowsInsertedRemoved(const QModelIndex
& parent
, int start
, int end
)
2261 kDebug()<<parent
<<", start="<<start
<<", end="<<end
;
2262 for (int row
= start
; row
<= end
; ++row
)
2264 const QModelIndex ix
= mapToSource(index(row
, 0, parent
));
2265 const Item item
= sourceModel()->data(ix
, AkonadiModel::ItemRole
).value
<Item
>();
2268 kDebug()<<"Valid item:"<<item
.remoteId();
2270 else{ const Collection collection
= data(ix
, AkonadiModel::CollectionRole
).value
<Collection
>();
2271 if (collection
.isValid())
2273 kDebug()<<"Got a collection!";
2279 QModelIndex
ItemListModel::index(int row
, int column
, const QModelIndex
& parent
) const
2281 if (parent
.isValid())
2282 return QModelIndex();
2283 return createIndex(row
, column
, mEvents
[row
]);
2286 bool ItemListModel::setData(const QModelIndex
& ix
, const QVariant
&, int role
)
2288 if (ix
.isValid() && role
== Qt::EditRole
)
2292 emit
dataChanged(index(row
, 0), index(row
, AkonadiModel::ColumnCount
- 1));
2299 Qt::ItemFlags
ItemListModel::flags(const QModelIndex
& index
) const
2301 if (!index
.isValid())
2302 return Qt::ItemIsEnabled
;
2303 return Qt::ItemIsEnabled
| Qt::ItemIsSelectable
| Qt::ItemIsEditable
| Qt::ItemIsDragEnabled
;
2306 /******************************************************************************
2307 * Return the index to a specified event.
2309 QModelIndex
ItemListModel::eventIndex(Entity::Id itemId
) const
2311 QModelIndexList list
= match(QModelIndex(), AkonadiModel::ItemIdRole
, itemId
, 1, Qt::MatchExactly
| Qt::MatchRecursive
);
2313 return QModelIndex();
2314 return index(list
[0].row(), 0, list
[0].parent());
2317 /******************************************************************************
2318 * Return the event in a specified row.
2320 KAEvent
ItemListModel::event(int row
) const
2322 return event(index(row
, 0));
2325 /******************************************************************************
2326 * Return the event referred to by an index.
2328 KAEvent
ItemListModel::event(const QModelIndex
& index
) const
2330 const Item item
= index
.data(EntityTreeModel::ItemRole
).value
<Item
>();
2331 if (item
.isValid() && item
.hasPayload
<KAEvent
>())
2333 KAEvent event
= item
.payload
<KAEvent
>();
2334 if (event
.isValid() && item
.hasAttribute
<EventAttribute
>())
2336 KAEvent::CmdErrType err
= item
.attribute
<EventAttribute
>()->commandError();
2337 event
.setCommandError(err
);
2344 /******************************************************************************
2345 * Insert rows into the model.
2347 bool ItemListModel::insertRows(int row
, int count
, const QModelIndex
& parent
)
2349 #warning Might instead need to iterate over all collections to find item count
2350 int oldCount
= rowCount();
2351 bool result
= EntityMimeTypeFilterModel::insertRows(row
, count
, parent
);
2352 if (!oldCount
&& count
)
2353 emit
haveEventsStatus(true);
2357 /******************************************************************************
2358 * Remove rows from the model.
2360 bool ItemListModel::removeRows(int row
, int count
, const QModelIndex
& parent
)
2362 bool result
= EntityMimeTypeFilterModel::removeRows(row
, count
, parent
);
2363 emit
haveEventsStatus(haveEvents());
2367 /******************************************************************************
2368 * Check whether the model contains any events.
2370 bool ItemListModel::haveEvents() const
2372 #warning Might instead need to iterate over all collections to find item count
2376 bool ItemListModel::filterAcceptsRow(int sourceRow
, const QModelIndex
& sourceParent
) const
2382 /*=============================================================================
2383 = Class: AlarmListModel
2384 = Filter proxy model containing all alarms of specified mime types in enabled
2386 Equivalent to AlarmListFilterModel
2387 =============================================================================*/
2388 AlarmListModel
* AlarmListModel::mAllInstance
= 0;
2390 AlarmListModel::AlarmListModel(QObject
* parent
)
2391 : ItemListModel(KAlarm::CalEvent::ACTIVE
| KAlarm::CalEvent::ARCHIVED
, parent
),
2392 mFilterTypes(KAlarm::CalEvent::ACTIVE
| KAlarm::CalEvent::ARCHIVED
)
2396 AlarmListModel::~AlarmListModel()
2398 if (this == mAllInstance
)
2402 AlarmListModel
* AlarmListModel::all()
2406 mAllInstance
= new AlarmListModel(AkonadiModel::instance());
2407 mAllInstance
->sort(TimeColumn
, Qt::AscendingOrder
);
2409 return mAllInstance
;
2412 void AlarmListModel::setEventTypeFilter(KAlarm::CalEvent::Types types
)
2414 // Ensure that the filter isn't applied to the 'all' instance, and that
2415 // 'types' doesn't include any disallowed alarm types
2417 types
= includedTypes();
2418 if (this != mAllInstance
2419 && types
!= mFilterTypes
&& (types
& includedTypes()) == types
)
2421 mFilterTypes
= types
;
2426 bool AlarmListModel::filterAcceptsRow(int sourceRow
, const QModelIndex
& sourceParent
) const
2428 if (!ItemListModel::filterAcceptsRow(sourceRow
, sourceParent
))
2430 if (mFilterTypes
== KAlarm::CalEvent::EMPTY
)
2432 int type
= sourceModel()->data(sourceModel()->index(sourceRow
, 0, sourceParent
), AkonadiModel::StatusRole
).toInt();
2433 return static_cast<KAlarm::CalEvent::Type
>(type
) & mFilterTypes
;
2436 bool AlarmListModel::filterAcceptsColumn(int sourceCol
, const QModelIndex
&) const
2438 return (sourceCol
!= AkonadiModel::TemplateNameColumn
);
2441 QModelIndex
AlarmListModel::mapFromSource(const QModelIndex
& sourceIndex
) const
2443 if (sourceIndex
.column() == AkonadiModel::TemplateNameColumn
)
2444 return QModelIndex();
2445 return ItemListModel::mapFromSource(sourceIndex
);
2449 /*=============================================================================
2450 = Class: TemplateListModel
2451 = Filter proxy model containing all alarm templates for specified alarm types
2452 = in enabled collections.
2453 Equivalent to TemplateListFilterModel
2454 =============================================================================*/
2455 TemplateListModel
* TemplateListModel::mAllInstance
= 0;
2457 TemplateListModel::TemplateListModel(QObject
* parent
)
2458 : ItemListModel(KAlarm::CalEvent::TEMPLATE
, parent
),
2459 mActionsEnabled(KAEvent::ACT_ALL
),
2460 mActionsFilter(KAEvent::ACT_ALL
)
2464 TemplateListModel::~TemplateListModel()
2466 if (this == mAllInstance
)
2470 TemplateListModel
* TemplateListModel::all()
2474 mAllInstance
= new TemplateListModel(AkonadiModel::instance());
2475 mAllInstance
->sort(TemplateNameColumn
, Qt::AscendingOrder
);
2477 return mAllInstance
;
2480 void TemplateListModel::setAlarmActionFilter(KAEvent::Actions types
)
2482 // Ensure that the filter isn't applied to the 'all' instance.
2483 if (this != mAllInstance
&& types
!= mActionsFilter
)
2485 mActionsFilter
= types
;
2490 void TemplateListModel::setAlarmActionsEnabled(KAEvent::Actions types
)
2492 // Ensure that the setting isn't applied to the 'all' instance.
2493 if (this != mAllInstance
&& types
!= mActionsEnabled
)
2495 mActionsEnabled
= types
;
2500 bool TemplateListModel::filterAcceptsRow(int sourceRow
, const QModelIndex
& sourceParent
) const
2502 if (mActionsFilter
== KAEvent::ACT_ALL
)
2504 QModelIndex sourceIndex
= sourceModel()->index(sourceRow
, 0, sourceParent
);
2505 KAEvent::Actions actions
= static_cast<KAEvent::Actions
>(sourceModel()->data(sourceIndex
, AkonadiModel::AlarmActionsRole
).toInt());
2506 return actions
& mActionsFilter
;
2509 bool TemplateListModel::filterAcceptsColumn(int sourceCol
, const QModelIndex
&) const
2511 return sourceCol
== AkonadiModel::TemplateNameColumn
2512 || sourceCol
== AkonadiModel::TypeColumn
;
2515 QModelIndex
TemplateListModel::mapFromSource(const QModelIndex
& sourceIndex
) const
2518 switch (sourceIndex
.column())
2520 case AkonadiModel::TypeColumn
:
2521 proxyColumn
= TypeColumn
;
2523 case AkonadiModel::TemplateNameColumn
:
2524 proxyColumn
= TemplateNameColumn
;
2527 return QModelIndex();
2529 QModelIndex ix
= ItemListModel::mapFromSource(sourceIndex
);
2530 return index(ix
.row(), proxyColumn
, ix
.parent());
2533 QModelIndex
TemplateListModel::mapToSource(const QModelIndex
& proxyIndex
) const
2536 switch (proxyIndex
.column())
2539 sourceColumn
= AkonadiModel::TypeColumn
;
2541 case TemplateNameColumn
:
2542 sourceColumn
= AkonadiModel::TemplateNameColumn
;
2545 return QModelIndex();
2547 QModelIndex ix
= ItemListModel::index(proxyIndex
.row(), sourceColumn
, proxyIndex
.parent());
2548 return ItemListModel::mapToSource(ix
);
2551 Qt::ItemFlags
TemplateListModel::flags(const QModelIndex
& index
) const
2553 Qt::ItemFlags f
= sourceModel()->flags(mapToSource(index
));
2554 if (mActionsEnabled
== KAEvent::ACT_ALL
)
2556 KAEvent::Actions actions
= static_cast<KAEvent::Actions
>(ItemListModel::data(index
, AkonadiModel::AlarmActionsRole
).toInt());
2557 if (!(actions
& mActionsEnabled
))
2558 f
= static_cast<Qt::ItemFlags
>(f
& ~(Qt::ItemIsEnabled
| Qt::ItemIsSelectable
));
2562 #include "akonadimodel.moc"
2563 #include "moc_akonadimodel.cpp"