File indexing completed on 2024-11-17 04:45:11
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "subscriptiondialog.h" 0009 0010 #include <QCheckBox> 0011 #include <QStandardItemModel> 0012 0013 #include "imapresource_debug.h" 0014 #include <KSharedConfig> 0015 #include <QLineEdit> 0016 0017 #include <KLocalizedString> 0018 0019 #include <KIMAP/LoginJob> 0020 #include <KIMAP/Session> 0021 #include <KIMAP/SubscribeJob> 0022 #include <KIMAP/UnsubscribeJob> 0023 0024 #include "imapaccount.h" 0025 #include "sessionuiproxy.h" 0026 #include <KConfigGroup> 0027 #include <QDialogButtonBox> 0028 #include <QPushButton> 0029 0030 #include <KWindowConfig> 0031 #include <QHeaderView> 0032 #include <QLabel> 0033 #include <QTreeView> 0034 #include <QVBoxLayout> 0035 #include <QWindow> 0036 0037 SubscriptionDialog::SubscriptionDialog(QWidget *parent, SubscriptionDialog::SubscriptionDialogOptions option) 0038 : QDialog(parent) 0039 , m_filter(new SubscriptionFilterProxyModel(this)) 0040 , m_model(new QStandardItemModel(this)) 0041 { 0042 auto topLayout = new QVBoxLayout(this); 0043 setModal(true); 0044 0045 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0046 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0047 okButton->setDefault(true); 0048 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0049 mUser1Button = new QPushButton(i18nc("@action:button", "Reload &List"), this); 0050 buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole); 0051 connect(buttonBox, &QDialogButtonBox::accepted, this, &SubscriptionDialog::slotAccepted); 0052 connect(buttonBox, &QDialogButtonBox::rejected, this, &SubscriptionDialog::reject); 0053 0054 mUser1Button->setEnabled(false); 0055 connect(mUser1Button, &QPushButton::clicked, this, &SubscriptionDialog::onReloadRequested); 0056 0057 auto mainWidget = new QWidget(this); 0058 auto mainLayout = new QVBoxLayout; 0059 mainLayout->setContentsMargins({}); 0060 mainWidget->setLayout(mainLayout); 0061 topLayout->addWidget(mainWidget); 0062 topLayout->addWidget(buttonBox); 0063 0064 m_enableSubscription = new QCheckBox(i18nc("@option:check", "Enable server-side subscriptions"), mainWidget); 0065 mainLayout->addWidget(m_enableSubscription); 0066 0067 auto filterBarLayout = new QHBoxLayout; 0068 mainLayout->addLayout(filterBarLayout); 0069 0070 filterBarLayout->addWidget(new QLabel(i18nc("@label search for a subscription", "Search:"), mainWidget)); 0071 0072 m_lineEdit = new QLineEdit(mainWidget); 0073 m_lineEdit->setClearButtonEnabled(true); 0074 connect(m_lineEdit, &QLineEdit::textChanged, this, &SubscriptionDialog::slotSearchPattern); 0075 filterBarLayout->addWidget(m_lineEdit); 0076 m_lineEdit->setFocus(); 0077 0078 auto checkBox = new QCheckBox(i18nc("@option:check", "Subscribed only"), mainWidget); 0079 connect(checkBox, &QCheckBox::stateChanged, m_filter, qOverload<int>(&SubscriptionFilterProxyModel::setIncludeCheckedOnly)); 0080 0081 filterBarLayout->addWidget(checkBox); 0082 0083 m_treeView = new QTreeView(mainWidget); 0084 m_treeView->header()->hide(); 0085 m_filter->setSourceModel(m_model); 0086 m_treeView->setModel(m_filter); 0087 mainLayout->addWidget(m_treeView); 0088 0089 connect(m_model, &QStandardItemModel::itemChanged, this, &SubscriptionDialog::onItemChanged); 0090 0091 if (option & SubscriptionDialog::AllowToEnableSubscription) { 0092 connect(m_enableSubscription, &QCheckBox::clicked, m_treeView, &QTreeView::setEnabled); 0093 } else { 0094 m_enableSubscription->hide(); 0095 } 0096 readConfig(); 0097 } 0098 0099 SubscriptionDialog::~SubscriptionDialog() 0100 { 0101 writeConfig(); 0102 } 0103 0104 void SubscriptionDialog::slotSearchPattern(const QString &pattern) 0105 { 0106 m_treeView->expandAll(); 0107 m_filter->setSearchPattern(pattern); 0108 } 0109 0110 void SubscriptionDialog::readConfig() 0111 { 0112 create(); // ensure a window is created 0113 windowHandle()->resize(QSize(500, 300)); 0114 KConfigGroup group(KSharedConfig::openStateConfig(), QStringLiteral("SubscriptionDialog")); 0115 KWindowConfig::restoreWindowSize(windowHandle(), group); 0116 resize(windowHandle()->size()); // workaround for QTBUG-40584 0117 } 0118 0119 void SubscriptionDialog::writeConfig() 0120 { 0121 KConfigGroup group(KSharedConfig::openStateConfig(), QStringLiteral("SubscriptionDialog")); 0122 KWindowConfig::saveWindowSize(windowHandle(), group); 0123 group.sync(); 0124 } 0125 0126 void SubscriptionDialog::setSubscriptionEnabled(bool enabled) 0127 { 0128 m_enableSubscription->setChecked(enabled); 0129 m_treeView->setEnabled(enabled); 0130 } 0131 0132 bool SubscriptionDialog::subscriptionEnabled() const 0133 { 0134 return m_enableSubscription->isChecked(); 0135 } 0136 0137 void SubscriptionDialog::connectAccount(const ImapAccount &account, const QString &password) 0138 { 0139 m_session = new KIMAP::Session(account.server(), account.port(), this); 0140 m_session->setUiProxy(SessionUiProxy::Ptr(new SessionUiProxy)); 0141 0142 auto login = new KIMAP::LoginJob(m_session); 0143 login->setUserName(account.userName()); 0144 login->setPassword(password); 0145 login->setEncryptionMode(account.encryptionMode()); 0146 login->setAuthenticationMode(account.authenticationMode()); 0147 0148 connect(login, &KIMAP::LoginJob::result, this, &SubscriptionDialog::onLoginDone); 0149 login->start(); 0150 } 0151 0152 bool SubscriptionDialog::isSubscriptionChanged() const 0153 { 0154 return m_subscriptionChanged; 0155 } 0156 0157 void SubscriptionDialog::onLoginDone(KJob *job) 0158 { 0159 if (!job->error()) { 0160 onReloadRequested(); 0161 } 0162 } 0163 0164 void SubscriptionDialog::onReloadRequested() 0165 { 0166 mUser1Button->setEnabled(false); 0167 m_itemsMap.clear(); 0168 m_model->clear(); 0169 0170 // we need a connection 0171 if (!m_session || m_session->state() != KIMAP::Session::Authenticated) { 0172 qCWarning(IMAPRESOURCE_LOG) << "SubscriptionDialog - got no connection"; 0173 mUser1Button->setEnabled(true); 0174 return; 0175 } 0176 0177 auto list = new KIMAP::ListJob(m_session); 0178 list->setOption(KIMAP::ListJob::IncludeUnsubscribed); 0179 connect(list, &KIMAP::ListJob::mailBoxesReceived, this, &SubscriptionDialog::onMailBoxesReceived); 0180 connect(list, &KIMAP::ListJob::result, this, &SubscriptionDialog::onFullListingDone); 0181 list->start(); 0182 } 0183 0184 void SubscriptionDialog::onMailBoxesReceived(const QList<KIMAP::MailBoxDescriptor> &mailBoxes, const QList<QList<QByteArray>> &flags) 0185 { 0186 const int numberOfMailBoxes(mailBoxes.size()); 0187 for (int i = 0; i < numberOfMailBoxes; i++) { 0188 KIMAP::MailBoxDescriptor mailBox = mailBoxes[i]; 0189 0190 const QStringList pathParts = mailBox.name.split(mailBox.separator); 0191 const QString separator = mailBox.separator; 0192 Q_ASSERT(separator.size() == 1); // that's what the spec says 0193 0194 QString parentPath; 0195 QString currentPath; 0196 const int numberOfPath(pathParts.size()); 0197 for (int j = 0; j < pathParts.size(); ++j) { 0198 const bool isDummy = (j != (numberOfPath - 1)); 0199 const bool isCheckable = !isDummy && !flags[i].contains("\\noselect"); 0200 0201 const QString pathPart = pathParts.at(j); 0202 currentPath += separator + pathPart; 0203 0204 if (m_itemsMap.contains(currentPath)) { 0205 if (!isDummy) { 0206 QStandardItem *item = m_itemsMap[currentPath]; 0207 item->setCheckable(isCheckable); 0208 } 0209 } else if (!parentPath.isEmpty()) { 0210 Q_ASSERT(m_itemsMap.contains(parentPath)); 0211 0212 QStandardItem *parentItem = m_itemsMap[parentPath]; 0213 0214 auto item = new QStandardItem(pathPart); 0215 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0216 item->setCheckable(isCheckable); 0217 item->setData(currentPath.mid(1), PathRole); 0218 parentItem->appendRow(item); 0219 m_itemsMap[currentPath] = item; 0220 } else { 0221 auto item = new QStandardItem(pathPart); 0222 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0223 item->setCheckable(isCheckable); 0224 item->setData(currentPath.mid(1), PathRole); 0225 m_model->appendRow(item); 0226 m_itemsMap[currentPath] = item; 0227 } 0228 0229 parentPath = currentPath; 0230 } 0231 } 0232 } 0233 0234 void SubscriptionDialog::onFullListingDone(KJob *job) 0235 { 0236 if (job->error()) { 0237 mUser1Button->setEnabled(true); 0238 return; 0239 } 0240 0241 auto list = new KIMAP::ListJob(m_session); 0242 list->setOption(KIMAP::ListJob::NoOption); 0243 connect(list, &KIMAP::ListJob::mailBoxesReceived, this, &SubscriptionDialog::onSubscribedMailBoxesReceived); 0244 connect(list, &KIMAP::ListJob::result, this, &SubscriptionDialog::onReloadDone); 0245 list->start(); 0246 } 0247 0248 void SubscriptionDialog::onSubscribedMailBoxesReceived(const QList<KIMAP::MailBoxDescriptor> &mailBoxes, const QList<QList<QByteArray>> &flags) 0249 { 0250 Q_UNUSED(flags) 0251 const int numberOfMailBoxes(mailBoxes.size()); 0252 for (int i = 0; i < numberOfMailBoxes; ++i) { 0253 KIMAP::MailBoxDescriptor mailBox = mailBoxes.at(i); 0254 QString descriptor = mailBox.separator + mailBox.name; 0255 0256 if (m_itemsMap.contains(descriptor)) { 0257 QStandardItem *item = m_itemsMap[mailBox.separator + mailBox.name]; 0258 item->setCheckState(Qt::Checked); 0259 item->setData(Qt::Checked, InitialStateRole); 0260 } 0261 } 0262 } 0263 0264 void SubscriptionDialog::onReloadDone(KJob *job) 0265 { 0266 Q_UNUSED(job) 0267 mUser1Button->setEnabled(true); 0268 } 0269 0270 void SubscriptionDialog::onItemChanged(QStandardItem *item) 0271 { 0272 QFont font = item->font(); 0273 font.setBold(item->checkState() != item->data(InitialStateRole).toInt()); 0274 item->setFont(font); 0275 } 0276 0277 void SubscriptionDialog::slotAccepted() 0278 { 0279 applyChanges(); 0280 accept(); 0281 } 0282 0283 void SubscriptionDialog::applyChanges() 0284 { 0285 QList<QStandardItem *> items = m_itemsMap.values(); 0286 0287 while (!items.isEmpty()) { 0288 QStandardItem *item = items.takeFirst(); 0289 0290 if (item->checkState() != item->data(InitialStateRole).toInt()) { 0291 if (item->checkState() == Qt::Checked) { 0292 qCDebug(IMAPRESOURCE_LOG) << "Subscribing" << item->data(PathRole); 0293 auto subscribe = new KIMAP::SubscribeJob(m_session); 0294 subscribe->setMailBox(item->data(PathRole).toString()); 0295 subscribe->exec(); 0296 } else { 0297 qCDebug(IMAPRESOURCE_LOG) << "Unsubscribing" << item->data(PathRole); 0298 auto unsubscribe = new KIMAP::UnsubscribeJob(m_session); 0299 unsubscribe->setMailBox(item->data(PathRole).toString()); 0300 unsubscribe->exec(); 0301 } 0302 0303 m_subscriptionChanged = true; 0304 } 0305 } 0306 } 0307 0308 SubscriptionFilterProxyModel::SubscriptionFilterProxyModel(QObject *parent) 0309 : QSortFilterProxyModel(parent) 0310 { 0311 setRecursiveFilteringEnabled(true); 0312 } 0313 0314 void SubscriptionFilterProxyModel::setSearchPattern(const QString &pattern) 0315 { 0316 if (m_pattern != pattern) { 0317 m_pattern = pattern; 0318 invalidate(); 0319 } 0320 } 0321 0322 void SubscriptionFilterProxyModel::setIncludeCheckedOnly(bool checkedOnly) 0323 { 0324 if (m_checkedOnly != checkedOnly) { 0325 m_checkedOnly = checkedOnly; 0326 invalidate(); 0327 } 0328 } 0329 0330 void SubscriptionFilterProxyModel::setIncludeCheckedOnly(int checkedOnlyState) 0331 { 0332 m_checkedOnly = (checkedOnlyState == Qt::Checked); 0333 invalidate(); 0334 } 0335 0336 bool SubscriptionFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 0337 { 0338 QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent); 0339 0340 const bool checked = sourceIndex.data(Qt::CheckStateRole).toInt() == Qt::Checked; 0341 0342 if (m_checkedOnly && !checked) { 0343 return false; 0344 } else if (!m_pattern.isEmpty()) { 0345 const QString text = sourceIndex.data(Qt::DisplayRole).toString(); 0346 return text.contains(m_pattern, Qt::CaseInsensitive); 0347 } else { 0348 return true; 0349 } 0350 } 0351 0352 #include "moc_subscriptiondialog.cpp"