File indexing completed on 2025-02-16 04:50:11

0001 /*
0002     SPDX-FileCopyrightText: 2015-2017 Krzysztof Nowicki <krissn@op.pl>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ewssubscriptionwidget.h"
0008 
0009 #include <QCheckBox>
0010 #include <QHBoxLayout>
0011 #include <QHeaderView>
0012 #include <QLineEdit>
0013 #include <QPushButton>
0014 #include <QSortFilterProxyModel>
0015 #include <QStandardItemModel>
0016 #include <QTreeView>
0017 #include <QVBoxLayout>
0018 
0019 #include "ewsclient.h"
0020 #include "ewsfindfolderrequest.h"
0021 #include "ewssettings.h"
0022 #include "ewssubscribedfoldersjob.h"
0023 #include <KLocalizedString>
0024 #include <KMessageWidget>
0025 
0026 class EwsSubscriptionFilterModel : public QSortFilterProxyModel
0027 {
0028     Q_OBJECT
0029 public:
0030     explicit EwsSubscriptionFilterModel(QObject *parent = nullptr);
0031     ~EwsSubscriptionFilterModel() override;
0032 public Q_SLOTS:
0033     void setFilterSelected(bool enabled);
0034 
0035 protected:
0036     [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
0037 
0038 private:
0039     [[nodiscard]] bool hasCheckedChildren(const QModelIndex &index) const;
0040     bool mFilterSelected;
0041 };
0042 
0043 class EwsSubscriptionWidgetPrivate : public QObject
0044 {
0045     Q_OBJECT
0046 public:
0047     EwsSubscriptionWidgetPrivate(EwsClient &client, EwsSettings *settings, QObject *parent);
0048     ~EwsSubscriptionWidgetPrivate() override;
0049 
0050     enum TreeModelRoles {
0051         ItemIdRole = Qt::UserRole + 1,
0052     };
0053 
0054     void populateFolderTree();
0055 
0056 public Q_SLOTS:
0057     void enableCheckBoxToggled(bool checked);
0058     void reloadFolderList(bool);
0059     void resetSelection(bool);
0060     void readFolderListFinished(KJob *job);
0061     void subscribedFoldersJobFinished(KJob *job);
0062     void treeItemChanged(QStandardItem *item);
0063     void filterTextChanged(const QString &text);
0064 
0065 public:
0066     bool mEnabled = true;
0067     QCheckBox *mEnableCheckBox = nullptr;
0068     QTreeView *mFolderTreeView = nullptr;
0069     QWidget *mSubContainer = nullptr;
0070     QPushButton *mRefreshButton = nullptr;
0071     EwsClient &mClient;
0072     KMessageWidget *mMsgWidget = nullptr;
0073     QStandardItemModel *mFolderTreeModel = nullptr;
0074     QHash<QString, QStandardItem *> mFolderItemHash;
0075     int mFolderListPendingRequests = 0;
0076     EwsFolder::List mFolders;
0077     EwsId::List mSubscribedIds;
0078     EwsId::List mOrigSubscribedIds;
0079     bool mSubscribedIdsRetrieved = true;
0080     EwsSubscriptionFilterModel *mFilterModel = nullptr;
0081     EwsSettings *mSettings = nullptr;
0082 
0083     EwsSubscriptionWidget *q_ptr = nullptr;
0084     Q_DECLARE_PUBLIC(EwsSubscriptionWidget)
0085 };
0086 
0087 EwsSubscriptionFilterModel::EwsSubscriptionFilterModel(QObject *parent)
0088     : QSortFilterProxyModel(parent)
0089     , mFilterSelected(false)
0090 {
0091     setRecursiveFilteringEnabled(true);
0092 }
0093 
0094 EwsSubscriptionFilterModel::~EwsSubscriptionFilterModel() = default;
0095 
0096 bool EwsSubscriptionFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0097 {
0098     bool show = true;
0099     if (mFilterSelected) {
0100         QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
0101 
0102         show = sourceIndex.data(Qt::CheckStateRole).toInt() == Qt::Checked;
0103         show |= hasCheckedChildren(sourceIndex);
0104     }
0105 
0106     if (!show) {
0107         return false;
0108     } else {
0109         return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
0110     }
0111 }
0112 
0113 bool EwsSubscriptionFilterModel::hasCheckedChildren(const QModelIndex &index) const
0114 {
0115     QModelIndex child;
0116     int row = 0;
0117     child = sourceModel()->index(row, 0, index);
0118     while (child.isValid()) {
0119         if (child.data(Qt::CheckStateRole).toInt() == Qt::Checked) {
0120             return true;
0121         } else if (hasCheckedChildren(child)) {
0122             return true;
0123         }
0124         child = sourceModel()->index(++row, 0, index);
0125     }
0126 
0127     return false;
0128 }
0129 
0130 void EwsSubscriptionFilterModel::setFilterSelected(bool enabled)
0131 {
0132     mFilterSelected = enabled;
0133     invalidateFilter();
0134 }
0135 
0136 EwsSubscriptionWidgetPrivate::EwsSubscriptionWidgetPrivate(EwsClient &client, EwsSettings *settings, QObject *parent)
0137     : QObject(parent)
0138     , mClient(client)
0139     , mSubscribedIdsRetrieved(false)
0140     , mSettings(settings)
0141 {
0142 }
0143 
0144 EwsSubscriptionWidgetPrivate::~EwsSubscriptionWidgetPrivate() = default;
0145 
0146 void EwsSubscriptionWidgetPrivate::enableCheckBoxToggled(bool checked)
0147 {
0148     mSubContainer->setEnabled(checked);
0149 }
0150 
0151 void EwsSubscriptionWidgetPrivate::reloadFolderList(bool)
0152 {
0153     if (mClient.isConfigured()) {
0154         auto req = new EwsFindFolderRequest(mClient, this);
0155         EwsFolderShape shape(EwsShapeIdOnly);
0156         shape << EwsPropertyField(QStringLiteral("folder:DisplayName"));
0157         shape << EwsPropertyField(QStringLiteral("folder:ParentFolderId"));
0158         req->setFolderShape(shape);
0159         req->setParentFolderId(EwsId(EwsDIdMsgFolderRoot));
0160         connect(req, &EwsRequest::result, this, &EwsSubscriptionWidgetPrivate::readFolderListFinished);
0161         req->start();
0162         mFolderListPendingRequests = 1;
0163         if (!mSubscribedIdsRetrieved) {
0164             auto job = new EwsSubscribedFoldersJob(mClient, mSettings, this);
0165             connect(job, &EwsRequest::result, this, &EwsSubscriptionWidgetPrivate::subscribedFoldersJobFinished);
0166             job->start();
0167             mFolderListPendingRequests++;
0168         }
0169         mRefreshButton->setEnabled(false);
0170     } else {
0171         mMsgWidget->setText(i18nc("@info", "Exchange server not configured."));
0172         mMsgWidget->setMessageType(KMessageWidget::Error);
0173         mMsgWidget->animatedShow();
0174     }
0175 }
0176 
0177 void EwsSubscriptionWidgetPrivate::readFolderListFinished(KJob *job)
0178 {
0179     if (job->error()) {
0180         mMsgWidget->setText(i18nc("@info", "Failed to retrieve folder list."));
0181         mMsgWidget->setMessageType(KMessageWidget::Error);
0182         mMsgWidget->animatedShow();
0183         mRefreshButton->setEnabled(true);
0184     } else {
0185         auto req = qobject_cast<EwsFindFolderRequest *>(job);
0186         Q_ASSERT(req);
0187 
0188         mFolders = req->folders();
0189 
0190         mFolderListPendingRequests--;
0191         if (mFolderListPendingRequests == 0) {
0192             mRefreshButton->setEnabled(true);
0193             populateFolderTree();
0194         }
0195     }
0196 }
0197 
0198 void EwsSubscriptionWidgetPrivate::subscribedFoldersJobFinished(KJob *job)
0199 {
0200     if (job->error()) {
0201         mMsgWidget->setText(i18nc("@info", "Failed to retrieve folder list."));
0202         mMsgWidget->setMessageType(KMessageWidget::Error);
0203         mMsgWidget->animatedShow();
0204         mRefreshButton->setEnabled(true);
0205     } else {
0206         auto req = qobject_cast<EwsSubscribedFoldersJob *>(job);
0207         Q_ASSERT(req);
0208 
0209         mSubscribedIds = req->folders();
0210 
0211         mFolderListPendingRequests--;
0212         mOrigSubscribedIds = mSubscribedIds;
0213         mSubscribedIdsRetrieved = true;
0214         if (mFolderListPendingRequests == 0) {
0215             mRefreshButton->setEnabled(true);
0216             populateFolderTree();
0217         }
0218     }
0219 }
0220 
0221 void EwsSubscriptionWidgetPrivate::populateFolderTree()
0222 {
0223     mFolderTreeModel->clear();
0224     mFolderItemHash.clear();
0225 
0226     for (const EwsFolder &folder : std::as_const(mFolders)) {
0227         auto item = new QStandardItem(folder[EwsFolderFieldDisplayName].toString());
0228         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0229         item->setCheckable(true);
0230         auto id = folder[EwsFolderFieldFolderId].value<EwsId>();
0231         item->setData(id.id(), ItemIdRole);
0232         if (mSubscribedIds.contains(EwsId(id.id()))) {
0233             item->setCheckState(Qt::Checked);
0234         }
0235         auto parentId = folder[EwsFolderFieldParentFolderId].value<EwsId>();
0236         if (parentId.type() != EwsId::Unspecified) {
0237             QStandardItem *parentItem = mFolderItemHash.value(parentId.id());
0238             if (parentItem) {
0239                 parentItem->appendRow(item);
0240             }
0241         }
0242         mFolderItemHash.insert(id.id(), item);
0243     }
0244 
0245     for (QStandardItem *item : std::as_const(mFolderItemHash)) {
0246         if (!item->parent()) {
0247             mFolderTreeModel->appendRow(item);
0248         }
0249     }
0250 }
0251 
0252 void EwsSubscriptionWidgetPrivate::treeItemChanged(QStandardItem *item)
0253 {
0254     EwsId id = EwsId(item->data(ItemIdRole).toString());
0255     if (item->checkState() == Qt::Checked) {
0256         mSubscribedIds += id;
0257     } else {
0258         mSubscribedIds.removeOne(id);
0259     }
0260 }
0261 
0262 void EwsSubscriptionWidgetPrivate::filterTextChanged(const QString &text)
0263 {
0264     mFilterModel->setFilterFixedString(text);
0265 }
0266 
0267 void EwsSubscriptionWidgetPrivate::resetSelection(bool)
0268 {
0269     mSubscribedIds = mOrigSubscribedIds;
0270     populateFolderTree();
0271 }
0272 
0273 EwsSubscriptionWidget::EwsSubscriptionWidget(EwsClient &client, EwsSettings *settings, QWidget *parent)
0274     : QWidget(parent)
0275     , d_ptr(new EwsSubscriptionWidgetPrivate(client, settings, this))
0276 {
0277     Q_D(EwsSubscriptionWidget);
0278 
0279     d->mEnabled = d->mSettings->serverSubscription();
0280 
0281     auto topLayout = new QVBoxLayout(this);
0282 
0283     d->mMsgWidget = new KMessageWidget(this);
0284     d->mMsgWidget->setVisible(false);
0285 
0286     d->mEnableCheckBox = new QCheckBox(i18nc("@option:check", "Enable server-side subscriptions"), this);
0287     d->mEnableCheckBox->setChecked(d->mEnabled);
0288 
0289     d->mSubContainer = new QWidget(this);
0290     auto subContainerLayout = new QVBoxLayout(d->mSubContainer);
0291     subContainerLayout->setContentsMargins({});
0292 
0293     auto filterLineEdit = new QLineEdit(this);
0294     filterLineEdit->setPlaceholderText(i18nc("@label:textbox", "Filter folders"));
0295 
0296     auto treeContainer = new QWidget(this);
0297     auto treeContainerLayout = new QHBoxLayout(treeContainer);
0298     treeContainerLayout->setContentsMargins({});
0299 
0300     d->mFolderTreeView = new QTreeView(this);
0301     d->mFolderTreeModel = new QStandardItemModel(this);
0302     d->mFilterModel = new EwsSubscriptionFilterModel(this);
0303     d->mFilterModel->setSourceModel(d->mFolderTreeModel);
0304     d->mFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
0305     d->mFilterModel->sort(0, Qt::AscendingOrder);
0306     d->mFolderTreeView->setModel(d->mFilterModel);
0307     d->mFolderTreeView->header()->hide();
0308 
0309     auto buttonContainer = new QWidget(this);
0310     auto buttonContainerLayout = new QVBoxLayout(buttonContainer);
0311     buttonContainerLayout->setContentsMargins({});
0312 
0313     d->mRefreshButton = new QPushButton(this);
0314     d->mRefreshButton->setText(i18nc("@action:button", "Reload &List"));
0315 
0316     auto resetButton = new QPushButton(this);
0317     resetButton->setText(i18nc("@action:button", "&Reset"));
0318 
0319     buttonContainerLayout->addWidget(d->mRefreshButton);
0320     buttonContainerLayout->addWidget(resetButton);
0321     buttonContainerLayout->addStretch();
0322 
0323     treeContainerLayout->addWidget(d->mFolderTreeView);
0324     treeContainerLayout->addWidget(buttonContainer);
0325 
0326     auto subOnlyCheckBox = new QCheckBox(i18nc("@option:check", "Subscribed only"), this);
0327 
0328     subContainerLayout->addWidget(filterLineEdit);
0329     subContainerLayout->addWidget(treeContainer);
0330     subContainerLayout->addWidget(subOnlyCheckBox);
0331 
0332     topLayout->addWidget(d->mMsgWidget);
0333     topLayout->addWidget(d->mEnableCheckBox);
0334     topLayout->addWidget(d->mSubContainer);
0335 
0336     connect(d->mEnableCheckBox, &QCheckBox::toggled, d, &EwsSubscriptionWidgetPrivate::enableCheckBoxToggled);
0337     connect(d->mRefreshButton, &QPushButton::clicked, d, &EwsSubscriptionWidgetPrivate::reloadFolderList);
0338     connect(resetButton, &QPushButton::clicked, d, &EwsSubscriptionWidgetPrivate::resetSelection);
0339     connect(d->mFolderTreeModel, &QStandardItemModel::itemChanged, d, &EwsSubscriptionWidgetPrivate::treeItemChanged);
0340     connect(filterLineEdit, &QLineEdit::textChanged, d, &EwsSubscriptionWidgetPrivate::filterTextChanged);
0341     connect(subOnlyCheckBox, &QCheckBox::toggled, d->mFilterModel, &EwsSubscriptionFilterModel::setFilterSelected);
0342 
0343     d->enableCheckBoxToggled(d->mEnabled);
0344     d->reloadFolderList(false);
0345 }
0346 
0347 EwsSubscriptionWidget::~EwsSubscriptionWidget() = default;
0348 
0349 QStringList EwsSubscriptionWidget::subscribedList() const
0350 {
0351     Q_D(const EwsSubscriptionWidget);
0352 
0353     QStringList list;
0354     list.reserve(d->mSubscribedIds.count());
0355     for (const EwsId &id : std::as_const(d->mSubscribedIds)) {
0356         list.append(id.id());
0357     }
0358 
0359     return list;
0360 }
0361 
0362 bool EwsSubscriptionWidget::subscriptionEnabled() const
0363 {
0364     Q_D(const EwsSubscriptionWidget);
0365 
0366     return d->mEnableCheckBox->isChecked();
0367 }
0368 
0369 bool EwsSubscriptionWidget::subscribedListValid() const
0370 {
0371     Q_D(const EwsSubscriptionWidget);
0372 
0373     return d->mSubscribedIdsRetrieved;
0374 }
0375 
0376 #include "ewssubscriptionwidget.moc"
0377 
0378 #include "moc_ewssubscriptionwidget.cpp"