File indexing completed on 2025-01-05 04:47:40
0001 /* 0002 SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com> 0003 SPDX-FileCopyrightText: 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "freebusyitemmodel.h" 0009 0010 #include <Akonadi/FreeBusyManager> 0011 0012 #include <KLocalizedString> 0013 0014 #include <QLocale> 0015 #include <QTimerEvent> 0016 0017 using namespace CalendarSupport; 0018 0019 class ItemPrivateData 0020 { 0021 public: 0022 ItemPrivateData(ItemPrivateData *parent) 0023 : parentItem(parent) 0024 { 0025 } 0026 0027 ~ItemPrivateData() 0028 { 0029 qDeleteAll(childItems); 0030 } 0031 0032 ItemPrivateData *child(int row) 0033 { 0034 return childItems.value(row); 0035 } 0036 0037 void appendChild(ItemPrivateData *item) 0038 { 0039 childItems.append(item); 0040 } 0041 0042 ItemPrivateData *removeChild(int row) 0043 { 0044 return childItems.takeAt(row); 0045 } 0046 0047 [[nodiscard]] int childCount() const 0048 { 0049 return childItems.count(); 0050 } 0051 0052 [[nodiscard]] int row() const 0053 { 0054 if (parentItem) { 0055 return parentItem->childItems.indexOf(const_cast<ItemPrivateData *>(this)); 0056 } 0057 return 0; 0058 } 0059 0060 ItemPrivateData *parent() 0061 { 0062 return parentItem; 0063 } 0064 0065 private: 0066 QList<ItemPrivateData *> childItems; 0067 ItemPrivateData *parentItem = nullptr; 0068 }; 0069 0070 class CalendarSupport::FreeBusyItemModelPrivate 0071 { 0072 public: 0073 ~FreeBusyItemModelPrivate() 0074 { 0075 delete mRootData; 0076 } 0077 0078 QTimer mReloadTimer; 0079 bool mForceDownload = false; 0080 QList<FreeBusyItem::Ptr> mFreeBusyItems; 0081 ItemPrivateData *mRootData = nullptr; 0082 }; 0083 0084 FreeBusyItemModel::FreeBusyItemModel(QObject *parent) 0085 : QAbstractItemModel(parent) 0086 , d(new CalendarSupport::FreeBusyItemModelPrivate) 0087 { 0088 qRegisterMetaType<KCalendarCore::Attendee>(); 0089 qRegisterMetaType<KCalendarCore::FreeBusy::Ptr>("KCalendarCore::FreeBusy::Ptr"); 0090 qRegisterMetaType<KCalendarCore::Period>("KCalendarCore::Period"); 0091 0092 Akonadi::FreeBusyManager *m = Akonadi::FreeBusyManager::self(); 0093 connect(m, &Akonadi::FreeBusyManager::freeBusyRetrieved, this, &FreeBusyItemModel::slotInsertFreeBusy); 0094 0095 connect(&d->mReloadTimer, &QTimer::timeout, this, &FreeBusyItemModel::autoReload); 0096 d->mReloadTimer.setSingleShot(true); 0097 0098 d->mRootData = new ItemPrivateData(nullptr); 0099 } 0100 0101 FreeBusyItemModel::~FreeBusyItemModel() = default; 0102 0103 QVariant FreeBusyItemModel::data(const QModelIndex &index, int role) const 0104 { 0105 if (!index.isValid()) { 0106 return {}; 0107 } 0108 0109 auto data = (ItemPrivateData *)index.internalPointer(); 0110 0111 if (data->parent() == d->mRootData) { 0112 int row = index.row(); 0113 if (row >= d->mFreeBusyItems.size()) { 0114 return {}; 0115 } 0116 0117 switch (role) { 0118 case Qt::DisplayRole: 0119 return d->mFreeBusyItems.at(row)->attendee().fullName(); 0120 case FreeBusyItemModel::AttendeeRole: 0121 return QVariant::fromValue(d->mFreeBusyItems.at(row)->attendee()); 0122 case FreeBusyItemModel::FreeBusyRole: 0123 if (d->mFreeBusyItems.at(row)->freeBusy()) { 0124 return QVariant::fromValue(d->mFreeBusyItems.at(row)->freeBusy()); 0125 } else { 0126 return {}; 0127 } 0128 default: 0129 return {}; 0130 } 0131 } 0132 0133 FreeBusyItem::Ptr fbitem = d->mFreeBusyItems.at(data->parent()->row()); 0134 if (!fbitem->freeBusy() || index.row() >= fbitem->freeBusy()->busyPeriods().size()) { 0135 return {}; 0136 } 0137 0138 KCalendarCore::FreeBusyPeriod period = fbitem->freeBusy()->fullBusyPeriods().at(index.row()); 0139 switch (role) { 0140 case Qt::DisplayRole: // return something to make modeltest happy 0141 return QStringLiteral("%1 - %2").arg(QLocale().toString(period.start().toLocalTime(), QLocale::ShortFormat), 0142 QLocale().toString(period.end().toLocalTime(), QLocale::ShortFormat)); 0143 case FreeBusyItemModel::FreeBusyPeriodRole: 0144 return QVariant::fromValue(period); 0145 default: 0146 return {}; 0147 } 0148 } 0149 0150 int FreeBusyItemModel::rowCount(const QModelIndex &parent) const 0151 { 0152 ItemPrivateData *parentData = nullptr; 0153 if (parent.column() > 0) { 0154 return 0; 0155 } 0156 0157 if (!parent.isValid()) { 0158 parentData = d->mRootData; 0159 } else { 0160 parentData = static_cast<ItemPrivateData *>(parent.internalPointer()); 0161 } 0162 0163 return parentData->childCount(); 0164 } 0165 0166 int FreeBusyItemModel::columnCount(const QModelIndex &parent) const 0167 { 0168 Q_UNUSED(parent) 0169 return 1; 0170 } 0171 0172 QModelIndex FreeBusyItemModel::index(int row, int column, const QModelIndex &parent) const 0173 { 0174 if (!hasIndex(row, column, parent)) { 0175 return {}; 0176 } 0177 0178 ItemPrivateData *parentData = nullptr; 0179 if (!parent.isValid()) { 0180 parentData = d->mRootData; 0181 } else { 0182 parentData = static_cast<ItemPrivateData *>(parent.internalPointer()); 0183 } 0184 0185 ItemPrivateData *childData = parentData->child(row); 0186 if (childData) { 0187 return createIndex(row, column, childData); 0188 } else { 0189 return {}; 0190 } 0191 } 0192 0193 QModelIndex FreeBusyItemModel::parent(const QModelIndex &child) const 0194 { 0195 if (!child.isValid()) { 0196 return {}; 0197 } 0198 0199 auto childData = static_cast<ItemPrivateData *>(child.internalPointer()); 0200 ItemPrivateData *parentData = childData->parent(); 0201 if (parentData == d->mRootData) { 0202 return {}; 0203 } 0204 0205 return createIndex(parentData->row(), 0, parentData); 0206 } 0207 0208 QVariant FreeBusyItemModel::headerData(int section, Qt::Orientation orientation, int role) const 0209 { 0210 if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section == 0) { 0211 return i18n("Attendee"); 0212 } 0213 return {}; 0214 } 0215 0216 void FreeBusyItemModel::addItem(const FreeBusyItem::Ptr &freebusy) 0217 { 0218 int row = d->mFreeBusyItems.size(); 0219 beginInsertRows(QModelIndex(), row, row); 0220 d->mFreeBusyItems.append(freebusy); 0221 auto data = new ItemPrivateData(d->mRootData); 0222 d->mRootData->appendChild(data); 0223 endInsertRows(); 0224 0225 if (freebusy->freeBusy() && !freebusy->freeBusy()->fullBusyPeriods().isEmpty()) { 0226 QModelIndex parent = index(row, 0); 0227 setFreeBusyPeriods(parent, freebusy->freeBusy()->fullBusyPeriods()); 0228 } 0229 updateFreeBusyData(freebusy); 0230 } 0231 0232 void FreeBusyItemModel::setFreeBusyPeriods(const QModelIndex &parent, const KCalendarCore::FreeBusyPeriod::List &list) 0233 { 0234 if (!parent.isValid()) { 0235 return; 0236 } 0237 0238 auto parentData = static_cast<ItemPrivateData *>(parent.internalPointer()); 0239 int fb_count = list.size(); 0240 int childCount = parentData->childCount(); 0241 QModelIndex first = index(0, 0, parent); 0242 QModelIndex last = index(childCount - 1, 0, parent); 0243 0244 if (childCount > 0 && fb_count < childCount) { 0245 beginRemoveRows(parent, fb_count - 1 < 0 ? 0 : fb_count - 1, childCount - 1); 0246 for (int i = childCount - 1; i > fb_count; --i) { 0247 delete parentData->removeChild(i); 0248 } 0249 endRemoveRows(); 0250 if (fb_count > 0) { 0251 last = index(fb_count - 1, 0, parent); 0252 Q_EMIT dataChanged(first, last); 0253 } 0254 } else if (fb_count > childCount) { 0255 beginInsertRows(parent, childCount, fb_count - 1); 0256 for (int i = childCount; i < fb_count; ++i) { 0257 auto childData = new ItemPrivateData(parentData); 0258 parentData->appendChild(childData); 0259 } 0260 endInsertRows(); 0261 if (childCount > 0) { 0262 last = index(childCount - 1, 0, parent); 0263 Q_EMIT dataChanged(first, last); 0264 } 0265 } else if (fb_count == childCount && fb_count > 0) { 0266 Q_EMIT dataChanged(first, last); 0267 } 0268 } 0269 0270 void FreeBusyItemModel::clear() 0271 { 0272 beginResetModel(); 0273 d->mFreeBusyItems.clear(); 0274 delete d->mRootData; 0275 d->mRootData = new ItemPrivateData(nullptr); 0276 endResetModel(); 0277 } 0278 0279 void FreeBusyItemModel::removeRow(int row) 0280 { 0281 beginRemoveRows(QModelIndex(), row, row); 0282 d->mFreeBusyItems.removeAt(row); 0283 ItemPrivateData *data = d->mRootData->removeChild(row); 0284 delete data; 0285 endRemoveRows(); 0286 } 0287 0288 void FreeBusyItemModel::removeItem(const FreeBusyItem::Ptr &freebusy) 0289 { 0290 int row = d->mFreeBusyItems.indexOf(freebusy); 0291 if (row >= 0) { 0292 removeRow(row); 0293 } 0294 } 0295 0296 void FreeBusyItemModel::removeAttendee(const KCalendarCore::Attendee &attendee) 0297 { 0298 FreeBusyItem::Ptr anItem; 0299 for (int i = 0; i < d->mFreeBusyItems.count(); ++i) { 0300 anItem = d->mFreeBusyItems[i]; 0301 if (anItem->attendee() == attendee) { 0302 if (anItem->updateTimerID() != 0) { 0303 killTimer(anItem->updateTimerID()); 0304 } 0305 removeRow(i); 0306 break; 0307 } 0308 } 0309 } 0310 0311 bool FreeBusyItemModel::containsAttendee(const KCalendarCore::Attendee &attendee) 0312 { 0313 FreeBusyItem::Ptr anItem; 0314 for (int i = 0; i < d->mFreeBusyItems.count(); ++i) { 0315 anItem = d->mFreeBusyItems[i]; 0316 if (anItem->attendee() == attendee) { 0317 return true; 0318 } 0319 } 0320 return false; 0321 } 0322 0323 void FreeBusyItemModel::updateFreeBusyData(const FreeBusyItem::Ptr &item) 0324 { 0325 if (item->isDownloading()) { 0326 // This item is already in the process of fetching the FB list 0327 return; 0328 } 0329 0330 if (item->updateTimerID() != 0) { 0331 // An update timer is already running. Reset it 0332 killTimer(item->updateTimerID()); 0333 } 0334 0335 // This item does not have a download running, and no timer is set 0336 // Do the download in one second 0337 item->setUpdateTimerID(startTimer(1000)); 0338 } 0339 0340 void FreeBusyItemModel::timerEvent(QTimerEvent *event) 0341 { 0342 killTimer(event->timerId()); 0343 for (FreeBusyItem::Ptr item : std::as_const(d->mFreeBusyItems)) { 0344 if (item->updateTimerID() == event->timerId()) { 0345 item->setUpdateTimerID(0); 0346 item->startDownload(d->mForceDownload); 0347 return; 0348 } 0349 } 0350 } 0351 0352 void FreeBusyItemModel::slotInsertFreeBusy(const KCalendarCore::FreeBusy::Ptr &fb, const QString &email) 0353 { 0354 if (!fb) { 0355 return; 0356 } 0357 0358 if (fb->fullBusyPeriods().isEmpty()) { 0359 return; 0360 } 0361 0362 fb->sortList(); 0363 0364 for (FreeBusyItem::Ptr item : std::as_const(d->mFreeBusyItems)) { 0365 if (item->email() == email) { 0366 item->setFreeBusy(fb); 0367 const int row = d->mFreeBusyItems.indexOf(item); 0368 const QModelIndex parent = index(row, 0); 0369 Q_EMIT dataChanged(parent, parent); 0370 setFreeBusyPeriods(parent, fb->fullBusyPeriods()); 0371 } 0372 } 0373 } 0374 0375 void FreeBusyItemModel::autoReload() 0376 { 0377 d->mForceDownload = false; 0378 reload(); 0379 } 0380 0381 void FreeBusyItemModel::reload() 0382 { 0383 for (FreeBusyItem::Ptr item : std::as_const(d->mFreeBusyItems)) { 0384 if (d->mForceDownload) { 0385 item->startDownload(d->mForceDownload); 0386 } else { 0387 updateFreeBusyData(item); 0388 } 0389 } 0390 } 0391 0392 void FreeBusyItemModel::triggerReload() 0393 { 0394 d->mReloadTimer.start(1000); 0395 } 0396 0397 void FreeBusyItemModel::cancelReload() 0398 { 0399 d->mReloadTimer.stop(); 0400 } 0401 0402 #include "moc_freebusyitemmodel.cpp"