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"