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"