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"