Fix akonadimodel.cpp:1: warning: unterminated character constant
[kdepim.git] / kalarm / akonadimodel.cpp
blob8b9ecdb3bc899f772ce453def3036a181502dfd8
1 #warning Read-only resource endlessly triggers alarm, disabling does not stop this
3 /*
4 * akonadimodel.cpp - KAlarm calendar file access using Akonadi
5 * Program: kalarm
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"
25 #include "kalarm.h"
26 #include "alarmtext.h"
27 #include "autoqpointer.h"
28 #include "collectionattribute.h"
29 #include "eventattribute.h"
30 #include "kaevent.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>
47 #include <klocale.h>
48 #include <kmessagebox.h>
50 #include <QApplication>
51 #include <QFileInfo>
52 #include <QMouseEvent>
53 #include <QHelpEvent>
54 #include <QToolTip>
55 #include <QTimer>
56 #include <QObject>
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 /*=============================================================================
69 = Class: AkonadiModel
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()
86 if (!mInstance)
87 mInstance = new AkonadiModel(new ChangeRecorder(qApp), qApp);
88 return mInstance;
91 /******************************************************************************
92 * Constructor.
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>();
111 if (!mTextIcon)
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());
121 #ifdef __GNUC__
122 #warning Only want to monitor collection properties, not content, when this becomes possible
123 #endif
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
143 switch (role)
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:
154 case Qt::FontRole:
155 case SortRole:
156 case ValueRole:
157 case StatusRole:
158 case AlarmActionsRole:
159 case AlarmActionRole:
160 case EnabledRole:
161 case CommandErrorRole:
162 case AlarmTypeRole:
163 case IsStandardRole:
164 break;
165 default:
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
173 switch (role)
175 case Qt::DisplayRole:
176 return displayName(collection);
177 case EnabledRole:
178 if (collection.hasAttribute<CollectionAttribute>())
179 return collection.attribute<CollectionAttribute>()->isEnabled();
180 return false;
181 case Qt::BackgroundRole:
183 QColor colour = backgroundColor(collection);
184 if (colour.isValid())
185 return colour;
186 break;
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;
197 break;
199 case Qt::FontRole:
201 if (!collection.hasAttribute<CollectionAttribute>())
202 break;
203 CollectionAttribute* attr = collection.attribute<CollectionAttribute>();
204 if (!attr->isEnabled())
205 break;
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
212 QFont font = mFont;
213 font.setBold(true);
214 return font;
216 break;
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",
232 "%1"
233 "<nl/>%2: <filename>%3</filename>"
234 "<nl/>%4, %5",
235 name, type, locn, disabled, readonly);
236 if (inactive || !writable)
237 return i18nc("@info:tooltip",
238 "%1"
239 "<nl/>%2: <filename>%3</filename>"
240 "<nl/>%4",
241 name, type, locn, (inactive ? disabled : readonly));
242 return i18nc("@info:tooltip",
243 "%1"
244 "<nl/>%2: <filename>%3</filename>",
245 name, type, locn);
247 case AlarmTypeRole:
248 return static_cast<int>(types(collection));
249 case IsStandardRole:
250 if (!collection.hasAttribute<CollectionAttribute>())
251 return 0;
252 return static_cast<int>(collection.attribute<CollectionAttribute>()->standard());
253 default:
254 break;
257 else
259 const Item item = index.data(ItemRole).value<Item>();
260 if (item.isValid())
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>())
266 return QVariant();
267 switch (role)
269 case StatusRole:
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;
277 return QVariant();
278 case CommandErrorRole:
279 if (!item.hasAttribute<EventAttribute>())
280 return KAEvent::CMD_NO_ERROR;
281 return item.attribute<EventAttribute>()->commandError();
282 default:
283 break;
285 int column = index.column();
286 if (role == Qt::WhatsThisRole)
287 return whatsThisText(column);
288 KAEvent event = item.payload<KAEvent>();
289 if (!event.isValid())
290 return QVariant();
291 if (role == AlarmActionsRole)
292 return event.actions();
293 if (role == AlarmActionRole)
294 return event.action();
295 bool calendarColour = false;
296 switch (column)
298 case TimeColumn:
299 switch (role)
301 case Qt::BackgroundRole:
302 calendarColour = true;
303 break;
304 case Qt::DisplayRole:
305 if (event.expired())
306 return alarmTimeText(event.startDateTime());
307 return alarmTimeText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER));
308 case SortRole:
310 DateTime due;
311 if (event.expired())
312 due = event.startDateTime();
313 else
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));
318 default:
319 break;
321 break;
322 case TimeToColumn:
323 switch (role)
325 case Qt::BackgroundRole:
326 calendarColour = true;
327 break;
328 case Qt::DisplayRole:
329 if (event.expired())
330 return QString();
331 return timeToAlarmText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER));
332 case SortRole:
334 if (event.expired())
335 return -1;
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;
343 break;
344 case RepeatColumn:
345 switch (role)
347 case Qt::BackgroundRole:
348 calendarColour = true;
349 break;
350 case Qt::DisplayRole:
351 return repeatText(event);
352 case Qt::TextAlignmentRole:
353 return Qt::AlignHCenter;
354 case SortRole:
355 return repeatOrder(event);
357 break;
358 case ColourColumn:
359 switch (role)
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)
369 return Qt::red;
371 break;
372 case Qt::ForegroundRole:
373 if (event.commandError() != KAEvent::CMD_NO_ERROR)
375 if (event.action() == KAEvent::COMMAND && !event.commandDisplay())
376 return Qt::white;
377 QColor colour = Qt::red;
378 int r, g, b;
379 event.bgColour().getRgb(&r, &g, &b);
380 if (r > 128 && g <= 128 && b <= 128)
381 colour = Qt::white;
382 return colour;
384 break;
385 case Qt::DisplayRole:
386 if (event.commandError() != KAEvent::CMD_NO_ERROR)
387 return QString::fromLatin1("!");
388 break;
389 case SortRole:
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'));
395 default:
396 break;
398 break;
399 case TypeColumn:
400 switch (role)
402 case Qt::DecorationRole:
404 QVariant v;
405 v.setValue(*eventIcon(event));
406 return v;
408 case Qt::TextAlignmentRole:
409 return Qt::AlignHCenter;
410 case Qt::SizeHintRole:
411 return mIconSize;
412 case Qt::AccessibleTextRole:
413 #ifdef __GNUC__
414 #warning Implement this
415 #endif
416 return QString();
417 case ValueRole:
418 return static_cast<int>(event.action());
419 case SortRole:
420 return QString("%1").arg(event.action(), 2, 10, QLatin1Char('0'));
422 break;
423 case TextColumn:
424 switch (role)
426 case Qt::BackgroundRole:
427 calendarColour = true;
428 break;
429 case Qt::DisplayRole:
430 case SortRole:
431 return AlarmText::summary(event, 1);
432 case Qt::ToolTipRole:
433 return AlarmText::summary(event);
434 default:
435 break;
437 break;
438 case TemplateNameColumn:
439 switch (role)
441 case Qt::BackgroundRole:
442 calendarColour = true;
443 break;
444 case Qt::DisplayRole:
445 return event.templateName();
446 case SortRole:
447 return event.templateName().toUpper();
449 break;
450 default:
451 break;
454 switch (role)
456 case Qt::ForegroundRole:
457 if (!event.enabled())
458 return Preferences::disabledColour();
459 if (event.expired())
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");
474 default:
475 case KAEvent::CMD_NO_ERROR:
476 break;
478 break;
479 case EnabledRole:
480 return event.enabled();
481 default:
482 break;
485 if (calendarColour)
487 QColor colour = backgroundColor(item.parentCollection());
488 if (colour.isValid())
489 return colour;
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())
503 return false;
504 Collection collection = index.data(CollectionRole).value<Collection>();
505 if (collection.isValid())
507 // This is a Collection row
508 bool updateCollection = false;
509 switch (role)
511 case Qt::FontRole:
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>();
515 return true;
516 case EnabledRole:
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)
522 return true;
523 attr->setEnabled(enabled);
524 updateCollection = true;
525 break;
527 case IsStandardRole:
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;
534 break;
535 default:
536 break;
538 if (updateCollection)
540 CollectionModifyJob* job = new CollectionModifyJob(collection, this);
541 connect(job, SIGNAL(result(KJob*)), this, SLOT(collectionJobDone(KJob*)));
542 return true;
545 else
547 Item item = index.data(ItemRole).value<Item>();
548 if (item.isValid())
550 bool updateItem = false;
551 switch (role)
553 case Qt::EditRole:
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()));
558 return true;
560 case CommandErrorRole:
562 KAEvent::CmdErrType err = static_cast<KAEvent::CmdErrType>(value.toInt());
563 switch (err)
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>())
571 return true;
572 EventAttribute* attr = item.attribute<EventAttribute>(Entity::AddIfMissing);
573 if (attr->commandError() == err)
574 return true;
575 attr->setCommandError(err);
576 updateItem = true;
577 // int row = index.row();
578 // emit dataChanged(this->index(row, 0, index.parent()), this->index(row, ColumnCount - 1, index.parent()));
579 break;
581 return false;
583 default:
584 break;
586 if (updateItem)
588 ItemModifyJob* job = new ItemModifyJob(item, this);
589 connect(job, SIGNAL(result(KJob*)), this, SLOT(itemJobDone(KJob*)));
590 return true;
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
602 switch (group)
604 case CollectionTreeHeaders:
605 return 1;
606 case ItemListHeaders:
607 return ColumnCount;
608 default:
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)
620 switch (group)
622 case CollectionTreeHeaders:
623 if (section != 0)
624 return QVariant();
625 if (role == Qt::DisplayRole)
626 return i18nc("@title:column", "Calendars");
627 break;
629 case ItemListHeaders:
630 if (section < 0 || section >= ColumnCount)
631 return QVariant();
632 if (role == Qt::DisplayRole)
634 switch (section)
636 case TimeColumn:
637 return i18nc("@title:column", "Time");
638 case TimeToColumn:
639 return i18nc("@title:column", "Time To");
640 case RepeatColumn:
641 return i18nc("@title:column", "Repeat");
642 case ColourColumn:
643 return QString();
644 case TypeColumn:
645 return QString();
646 case TextColumn:
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);
654 break;
656 default:
657 break;
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;
716 if (mins < 0)
717 return QString();
718 char minutes[3] = "00";
719 minutes[0] = (mins%60) / 10 + '0';
720 minutes[1] = (mins%60) % 10 + '0';
721 if (mins < 24*60)
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)
734 int start = -1;
735 int end;
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();
741 if (isItem)
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.
747 if (start < 0)
748 start = row;
749 end = row;
750 continue;
753 if (start >= 0)
754 emit dataChanged(index(start, startColumn, parent), index(end, endColumn, parent));
755 start = -1;
756 if (!isItem)
757 signalDataChanged(checkFunc, startColumn, endColumn, ix);
760 if (start >= 0)
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&)
784 kDebug();
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>();
796 if (event.isValid())
797 return !event.enabled();
799 return false;
802 void AkonadiModel::slotUpdateDisabledColour(const QColor&)
804 kDebug();
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
811 * non-holidays.
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();
821 return true;
824 return false;
827 void AkonadiModel::slotUpdateHolidays()
829 kDebug();
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();
847 return true;
850 return false;
853 void AkonadiModel::slotUpdateWorkingHours()
855 kDebug();
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);
868 if (!list.isEmpty())
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);
880 if (ix.isValid())
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>())
890 return QColor();
891 return collection.attribute<CollectionAttribute>()->backgroundColor();
894 /******************************************************************************
895 * Return the display name for the collection.
897 QString AkonadiModel::displayName(const Akonadi::Collection& collection) const
899 QString name;
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);
924 return repeatText;
927 /******************************************************************************
928 * Return a string for sorting the repetition column.
930 QString AkonadiModel::repeatOrder(const KAEvent& event) const
932 int repeatOrder = 0;
933 int repeatInterval = 0;
934 if (event.repeatAtLogin())
935 repeatOrder = 1;
936 else
938 repeatInterval = event.recurInterval();
939 switch (event.recurType())
941 case KARecurrence::MINUTELY:
942 repeatOrder = 2;
943 break;
944 case KARecurrence::DAILY:
945 repeatOrder = 3;
946 break;
947 case KARecurrence::WEEKLY:
948 repeatOrder = 4;
949 break;
950 case KARecurrence::MONTHLY_DAY:
951 case KARecurrence::MONTHLY_POS:
952 repeatOrder = 5;
953 break;
954 case KARecurrence::ANNUAL_DATE:
955 case KARecurrence::ANNUAL_POS:
956 repeatOrder = 6;
957 break;
958 case KARecurrence::NO_RECUR:
959 default:
960 break;
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())
973 case KAAlarm::FILE:
974 return mFileIcon;
975 case KAAlarm::EMAIL:
976 return mEmailIcon;
977 case KAAlarm::AUDIO:
978 return mAudioIcon;
979 case KAAlarm::COMMAND:
980 if (!event.commandDisplay())
981 return mCommandIcon;
982 // fall through to MESSAGE
983 case KAAlarm::MESSAGE:
984 default:
985 return mTextIcon;
989 /******************************************************************************
990 * Returns the QWhatsThis text for a specified column.
992 QString AkonadiModel::whatsThisText(int column) const
994 switch (column)
996 case TimeColumn:
997 return i18nc("@info:whatsthis", "Next scheduled date and time of the alarm");
998 case TimeToColumn:
999 return i18nc("@info:whatsthis", "How long until the next scheduled trigger of the alarm");
1000 case RepeatColumn:
1001 return i18nc("@info:whatsthis", "How often the alarm recurs");
1002 case ColourColumn:
1003 return i18nc("@info:whatsthis", "Background color of alarm message");
1004 case TypeColumn:
1005 return i18nc("@info:whatsthis", "Alarm type (message, file, command or email)");
1006 case TextColumn:
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");
1010 default:
1011 return QString();
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);
1021 if (list.isEmpty())
1022 return QModelIndex();
1023 return list[0];
1026 #if 0
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
1032 KAEvent::List list;
1033 QModelIndex ix = modelIndexForCollection(this, collection);
1034 if (ix.isValid())
1035 getChildEvents(ix, type, list);
1036 return 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>();
1048 if (item.isValid())
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);
1060 events += event;
1064 else
1066 Collection c = ix.data(CollectionRole).value<Collection>();
1067 if (c.isValid())
1068 getChildEvents(ix, type, events);
1072 #endif
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);
1085 return event;
1087 return KAEvent();
1090 KAEvent AkonadiModel::event(Item::Id itemId) const
1092 QModelIndexList list = match(QModelIndex(), EntityTreeModel::ItemIdRole, itemId, 1, Qt::MatchExactly | Qt::MatchRecursive);
1093 if (list.isEmpty())
1094 return KAEvent();
1095 return event(list.at(0));
1098 #if 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
1120 bool cancelled;
1121 Collection collection = destination(type, Collection::CanCreateItem, promptParent, noPrompt, &cancelled);
1122 if (!collection.isValid())
1124 delete event;
1125 if (cancelled)
1126 return Cancelled;
1127 kDebug() << "No collection";
1128 return Failed;
1130 if (!addEvent(event, collection))
1132 kDebug() << "Failed";
1133 return Failed; // event was deleted by addEvent()
1135 return Success;
1137 #endif
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)
1151 bool ok = true;
1152 for (int i = 0, count = events.count(); i < count; ++i)
1153 ok = ok && addEvent(*events[i], collection);
1154 return ok;
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)
1168 Item item;
1169 if (!setItemPayload(item, event, collection))
1170 return false;
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();
1175 job->start();
1176 return true;
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);
1194 if (list.isEmpty())
1195 return false;
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))
1199 return false;
1200 ItemModifyJob* job = new ItemModifyJob(item);
1201 connect(job, SIGNAL(result(KJob*)), SLOT(itemJobDone(KJob*)));
1202 mPendingItems[job] = itemId;
1203 job->start();
1204 return true;
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)
1215 QString mimetype;
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";
1226 return false;
1228 event.clearUpdated();
1229 item.setMimeType(mimetype);
1230 item.setPayload<KAEvent>(event);
1231 return true;
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);
1244 if (list.isEmpty())
1245 return false;
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;
1250 job->start();
1251 return true;
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);
1267 if (j->error())
1269 QString errMsg;
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.");
1277 else
1278 Q_ASSERT(0);
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
1284 else
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;
1295 if (j->error())
1297 QString errMsg;
1298 QByteArray jobClass = j->metaObject()->className();
1299 if (jobClass == "Akonadi::CollectionModifyJob")
1301 Collection c = static_cast<CollectionModifyJob*>(j)->collection();
1302 id = c.id();
1303 errMsg = i18nc("@info", "Could not update calendar <resource>%1</resource>.", displayName(c));
1305 else
1306 Q_ASSERT(0);
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)
1350 EventList events;
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>());
1363 return events;
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)
1372 kDebug();
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;
1391 first = false;
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>())
1413 return;
1414 KAEvent event = item.payload<KAEvent>();
1415 if (!event.isValid())
1416 return;
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()));
1426 break;
1431 /******************************************************************************
1432 * Called to emit a signal when an event in the monitored collections has
1433 * changed.
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);
1449 if (!ix.isValid())
1450 return QModelIndex();
1451 return ix;
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));
1460 if (!ix.isValid())
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);
1471 if (list.isEmpty())
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;
1486 return types;
1489 /******************************************************************************
1490 * Check whether the alarm types in a calendar correspond with a collection's
1491 * mime types.
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);
1497 if (etypes)
1499 bool have = false;
1500 bool other = false;
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]);
1505 if (etypes & s)
1506 have = true;
1507 else
1508 other = true;
1509 if (have && other)
1510 break;
1512 if (!have && other)
1513 return false; // contains only wrong alarm types
1515 return true;
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
1526 Q_OBJECT
1527 public:
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;
1534 protected:
1535 virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const;
1537 private:
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),
1544 mMimeType(),
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;
1558 invalidateFilter();
1562 void CollectionMimeTypeFilterModel::setFilterWritable(bool writable)
1564 if (writable != mWritableOnly)
1566 mWritableOnly = writable;
1567 invalidateFilter();
1571 bool CollectionMimeTypeFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
1573 if (!EntityMimeTypeFilterModel::filterAcceptsRow(sourceRow, sourceParent))
1574 return false;
1575 if (!mWritableOnly && mMimeType.isEmpty())
1576 return true;
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)
1581 return false;
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()
1653 if (!mInstance)
1654 mInstance = new CollectionCheckListModel(qApp);
1655 return mInstance;
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";
1723 continue;
1725 const CollectionAttribute* attr = collection.attribute<CollectionAttribute>();
1726 if (!attr->isEnabled())
1727 {kDebug()<<"Already disabled";
1728 continue;
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."));
1737 continue;
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."));
1746 continue;
1748 if (KMessageBox::warningContinueCancel(static_cast<QWidget*>(parent()),
1749 i18nc("@info", "Do you really want to disable your default calendar?"))
1750 == KMessageBox::Cancel)
1751 continue;
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),
1765 mMimeType()
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;
1776 invalidateFilter();
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())
1796 return true;
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)
1808 : QListView(parent)
1810 setModel(model);
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())
1839 clearSelection();
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('@');
1857 if (i > 0)
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.
1877 if (k > 0)
1878 toolTip.remove(i, k + 1 - i);
1880 else
1882 toolTip.remove(k, 1);
1883 toolTip.remove(i, 1);
1886 QToolTip::showText(he->globalPos(), toolTip, this);
1887 return true;
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()
1909 if (!mInstance)
1910 mInstance = new CollectionControlModel(qApp);
1911 return mInstance;
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())
1963 if (value)
1965 const Collection::List cols = collections();
1966 foreach (const Collection& c, cols)
1968 if (c.id() == collection.id())
1969 return;
1971 addCollection(collection);
1973 else
1974 removeCollection(collection);
1975 AkonadiModel* model = static_cast<AkonadiModel*>(sourceModel());
1976 model->setData(model->collectionIndex(collection), value, AkonadiModel::EnabledRole);
1978 #if 0
1979 QModelIndex ix = modelIndexForCollection(this, collection);
1980 if (ix.isValid())
1981 selectionModel()->select(mapFromSource(ix), (enabled ? QItemSelectionModel::Select : QItemSelectionModel::Deselect));
1982 #endif
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)
1993 return false;
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))
2009 return cols[i];
2011 return Collection();
2014 /******************************************************************************
2015 * Return whether a collection is the standard collection for a specified
2016 * mime type.
2018 bool CollectionControlModel::isStandard(Akonadi::Collection& collection, KAlarm::CalEvent::Type type)
2020 if (!instance()->collections().contains(collection))
2021 return false;
2022 if (!collection.hasAttribute<CollectionAttribute>())
2023 return false;
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();
2047 if (standard)
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))
2053 return;
2054 KAlarm::CalEvent::Types ctypes = collection.hasAttribute<CollectionAttribute>()
2055 ? collection.attribute<CollectionAttribute>()->standard() : KAlarm::CalEvent::EMPTY;
2056 if (ctypes & type)
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;
2065 else
2067 types = cols[i].hasAttribute<CollectionAttribute>()
2068 ? cols[i].attribute<CollectionAttribute>()->standard() : KAlarm::CalEvent::EMPTY;
2069 if (!(types & type))
2070 continue;
2071 types &= ~type;
2073 QModelIndex index = model->collectionIndex(cols[i]);
2074 model->setData(index, static_cast<int>(types), AkonadiModel::IsStandardRole);
2077 else
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;
2083 if (types & type)
2085 types &= ~type;
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();
2100 if (types)
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))
2106 return;
2107 KAlarm::CalEvent::Types t = collection.hasAttribute<CollectionAttribute>()
2108 ? collection.attribute<CollectionAttribute>()->standard() : KAlarm::CalEvent::EMPTY;
2109 if (t == types)
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)
2116 t = types;
2118 else
2120 t = cols[i].hasAttribute<CollectionAttribute>()
2121 ? cols[i].attribute<CollectionAttribute>()->standard() : KAlarm::CalEvent::EMPTY;
2122 if (!(t & types))
2123 continue;
2124 t &= ~types;
2126 QModelIndex index = model->collectionIndex(cols[i]);
2127 model->setData(index, static_cast<int>(t), AkonadiModel::IsStandardRole);
2130 else
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)
2150 if (cancelled)
2151 *cancelled = false;
2152 Collection standard;
2153 if (type == KAlarm::CalEvent::EMPTY)
2154 return standard;
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()))
2159 return standard;
2161 // Prompt for which collection to use
2162 CollectionListModel* model = new CollectionListModel(promptParent);
2163 model->setFilterWritable(true);
2164 model->setEventTypeFilter(type);
2165 Collection col;
2166 switch (model->rowCount())
2168 case 0:
2169 break;
2170 case 1:
2171 col = model->collection(0);
2172 break;
2173 default:
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);
2181 if (dlg->exec())
2182 col = dlg->selectedCollection();
2183 if (!col.isValid() && cancelled)
2184 *cancelled = true;
2187 return col;
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)))
2203 result += cols[i];
2205 return result;
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);
2233 if (allowed)
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
2249 #if 0
2250 if (parent.isValid())
2251 return 0;
2252 #endif
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>();
2266 if (item.isValid())
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!";
2278 #if 0
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)
2290 //??? update event
2291 int row = ix.row();
2292 emit dataChanged(index(row, 0), index(row, AkonadiModel::ColumnCount - 1));
2293 return true;
2295 return false;
2297 #endif
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);
2312 if (list.isEmpty())
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);
2339 return event;
2341 return KAEvent();
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);
2354 return result;
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());
2364 return result;
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
2373 return rowCount();
2376 bool ItemListModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
2378 return true;
2382 /*=============================================================================
2383 = Class: AlarmListModel
2384 = Filter proxy model containing all alarms of specified mime types in enabled
2385 = collections.
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)
2399 mAllInstance = 0;
2402 AlarmListModel* AlarmListModel::all()
2404 if (!mAllInstance)
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
2416 if (!types)
2417 types = includedTypes();
2418 if (this != mAllInstance
2419 && types != mFilterTypes && (types & includedTypes()) == types)
2421 mFilterTypes = types;
2422 invalidateFilter();
2426 bool AlarmListModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
2428 if (!ItemListModel::filterAcceptsRow(sourceRow, sourceParent))
2429 return false;
2430 if (mFilterTypes == KAlarm::CalEvent::EMPTY)
2431 return false;
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)
2467 mAllInstance = 0;
2470 TemplateListModel* TemplateListModel::all()
2472 if (!mAllInstance)
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;
2486 filterChanged();
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;
2496 filterChanged();
2500 bool TemplateListModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
2502 if (mActionsFilter == KAEvent::ACT_ALL)
2503 return true;
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
2517 int proxyColumn;
2518 switch (sourceIndex.column())
2520 case AkonadiModel::TypeColumn:
2521 proxyColumn = TypeColumn;
2522 break;
2523 case AkonadiModel::TemplateNameColumn:
2524 proxyColumn = TemplateNameColumn;
2525 break;
2526 default:
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
2535 int sourceColumn;
2536 switch (proxyIndex.column())
2538 case TypeColumn:
2539 sourceColumn = AkonadiModel::TypeColumn;
2540 break;
2541 case TemplateNameColumn:
2542 sourceColumn = AkonadiModel::TemplateNameColumn;
2543 break;
2544 default:
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)
2555 return f;
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));
2559 return f;
2562 #include "akonadimodel.moc"
2563 #include "moc_akonadimodel.cpp"
2565 // vim: et sw=4: