File indexing completed on 2024-11-24 04:53:04

0001 /* Copyright (C) 2006 - 2016 Jan Kundrát <jkt@kde.org>
0002    Copyright (C) 2014        Luke Dashjr <luke+trojita@dashjr.org>
0003    Copyright (C) 2012        Mohammed Nafees <nafees.technocool@gmail.com>
0004    Copyright (C) 2013        Pali Rohár <pali.rohar@gmail.com>
0005 
0006    This file is part of the Trojita Qt IMAP e-mail client,
0007    http://trojita.flaska.net/
0008 
0009    This program is free software; you can redistribute it and/or
0010    modify it under the terms of the GNU General Public License as
0011    published by the Free Software Foundation; either version 2 of
0012    the License or (at your option) version 3 or any later version
0013    accepted by the membership of KDE e.V. (or its successor approved
0014    by the membership of KDE e.V.), which shall act as a proxy
0015    defined in Section 14 of version 3 of the license.
0016 
0017    This program is distributed in the hope that it will be useful,
0018    but WITHOUT ANY WARRANTY; without even the implied warranty of
0019    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0020    GNU General Public License for more details.
0021 
0022    You should have received a copy of the GNU General Public License
0023    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0024 */
0025 #include <QCheckBox>
0026 #include <QColorDialog>
0027 #include <QComboBox>
0028 #include <QDataWidgetMapper>
0029 #include <QDialogButtonBox>
0030 #include <QDebug>
0031 #include <QDir>
0032 #include <QFormLayout>
0033 #include <QGroupBox>
0034 #include <QInputDialog>
0035 #include <QLineEdit>
0036 #include <QListWidget>
0037 #include <QMessageBox>
0038 #include <QProcess>
0039 #include <QPushButton>
0040 #include <QRadioButton>
0041 #include <QSpinBox>
0042 #include <QStackedWidget>
0043 #include <QStandardItemModel>
0044 #include <QTabWidget>
0045 #include <QToolTip>
0046 #include <QVBoxLayout>
0047 #include "SettingsDialog.h"
0048 #include "ColoredItemDelegate.h"
0049 #include "Common/InvokeMethod.h"
0050 #include "Common/PortNumbers.h"
0051 #include "Common/SettingsNames.h"
0052 #include "Gui/Util.h"
0053 #include "Gui/Window.h"
0054 #include "Imap/Model/ImapAccess.h"
0055 #include "MSA/Account.h"
0056 #include "Plugins/AddressbookPlugin.h"
0057 #include "Plugins/PasswordPlugin.h"
0058 #include "Plugins/PluginManager.h"
0059 #include "UiUtils/IconLoader.h"
0060 #include "UiUtils/PasswordWatcher.h"
0061 #include "ShortcutHandler/ShortcutHandler.h"
0062 
0063 namespace Gui
0064 {
0065 
0066 QString SettingsDialog::warningStyleSheet = Util::cssWarningBorder() + QStringLiteral("font-weight: bold;");
0067 
0068 /** @short Check a text field for being non empty. If it's empty, show an error to the user. */
0069 template<typename T>
0070 bool checkProblemWithEmptyTextField(T *field, const QString &message)
0071 {
0072     if (field->text().isEmpty()) {
0073         QToolTip::showText(field->mapToGlobal(QPoint(10, field->height() / 2)), message, 0);
0074         return true;
0075     } else {
0076         return false;
0077     }
0078 }
0079 
0080 SettingsDialog::SettingsDialog(MainWindow *parent, Composer::SenderIdentitiesModel *identitiesModel,
0081         Imap::Mailbox::FavoriteTagsModel *favoriteTagsModel, QSettings *settings):
0082     QDialog(parent), mainWindow(parent), m_senderIdentities(identitiesModel), m_favoriteTags(favoriteTagsModel),
0083         m_settings(settings)
0084 {
0085     setWindowTitle(tr("Settings"));
0086 
0087     QVBoxLayout *layout = new QVBoxLayout(this);
0088     stack = new QTabWidget(this);
0089     layout->addWidget(stack);
0090     stack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0091 
0092     addPage(new GeneralPage(this, *m_settings, m_senderIdentities), tr("&General"));
0093     addPage(new ImapPage(this, *m_settings), tr("I&MAP"));
0094     addPage(new CachePage(this, *m_settings), tr("&Offline"));
0095     addPage(new OutgoingPage(this, *m_settings), tr("&SMTP"));
0096     addPage(new FavoriteTagsPage(this, *m_settings, m_favoriteTags), tr("Favorite &tags"));
0097 
0098     buttons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, Qt::Horizontal, this);
0099     connect(buttons, &QDialogButtonBox::accepted, this, &SettingsDialog::accept);
0100     connect(buttons, &QDialogButtonBox::rejected, this, &SettingsDialog::reject);
0101     layout->addWidget(buttons);
0102 
0103     EMIT_LATER_NOARG(this, reloadPasswordsRequested);
0104 }
0105 
0106 void SettingsDialog::setOriginalPlugins(const QString &passwordPlugin, const QString &addressBookPlugin,
0107                                         const QString &spellcheckerPlugin)
0108 {
0109     m_originalPasswordPlugin = passwordPlugin;
0110     m_originalAddressbookPlugin = addressBookPlugin;
0111     m_originalSpellcheckerPlugin = spellcheckerPlugin;
0112 }
0113 
0114 void SettingsDialog::adjustSizeToScrollAreas()
0115 {
0116     QScrollArea *area = qobject_cast<QScrollArea*>(sender());
0117     Q_ASSERT(area && area->widget());
0118 
0119     // task #A: figure the "minimum" size for the tabwidget
0120 
0121     // #A.1: search scrollareas and align their size to their content
0122     // update size of the widget in the tabbed scrollarea
0123     area->widget()->adjustSize();
0124 
0125     // figure the size demand of this scroll area (content + margins)
0126     const auto margins = area->contentsMargins();
0127     QSize minSize(area->widget()->size() + QSize(margins.left() + margins.right(), margins.top() + margins.bottom()));
0128 
0129     // TODO: clamp this to 640x480 or QDesktopWidget::availableGeometry() dependent?
0130 
0131     // do not shrink (prevent nasty size jumps for no reason)
0132     minSize.setWidth(qMax(area->width(), minSize.width()));
0133     minSize.setHeight(qMax(area->height(), minSize.height()));
0134 
0135     // task #B: find the QStackedWidget inside the QTabWidget to determine its margins
0136     Q_FOREACH(const QObject *o, stack->children()) {
0137         if (const QStackedWidget *actualStack = qobject_cast<const QStackedWidget*>(o)) {
0138             minSize.setWidth(minSize.width() + stack->width() - actualStack->width());
0139             minSize.setHeight(minSize.height() + stack->height() - actualStack->height());
0140             break;
0141         }
0142     }
0143 
0144     // task #C: convince the dialog to the new size
0145     // #C.1: arrest the tabwidget
0146     stack->setMinimumSize(minSize);
0147     // #C.2: force a relayout of the dialog (do NOT use "adjustSize", which may still shrink)
0148     layout()->activate();
0149     // #C.3: release the tabwidget minimum size
0150     stack->setMinimumSize(QSize(0, 0));
0151 }
0152 
0153 Plugins::PluginManager *SettingsDialog::pluginManager()
0154 {
0155     return mainWindow->pluginManager();
0156 }
0157 
0158 Imap::ImapAccess *SettingsDialog::imapAccess()
0159 {
0160     return mainWindow->imapAccess();
0161 }
0162 
0163 void SettingsDialog::accept()
0164 {
0165     m_saveSignalCount = 0;
0166 
0167     Q_FOREACH(ConfigurationWidgetInterface *page, pages) {
0168         if (!page->checkValidity()) {
0169             stack->setCurrentWidget(page->asWidget());
0170             return;
0171         }
0172         connect(page->asWidget(), SIGNAL(saved()), this, SLOT(slotAccept())); // new-signal-slot: we're abusing the type system a bit here, cannot use the new syntax
0173         ++m_saveSignalCount;
0174     }
0175 
0176 #ifndef Q_OS_WIN
0177     // Try to wour around QSettings' inability to set umask for its file access. We don't want to set umask globally.
0178     QFile settingsFile(m_settings->fileName());
0179     settingsFile.setPermissions(QFile::ReadUser | QFile::WriteUser);
0180 #endif
0181 
0182     buttons->setEnabled(false);
0183     Q_FOREACH(ConfigurationWidgetInterface *page, pages) {
0184         page->asWidget()->setEnabled(false);
0185     }
0186 
0187     Q_FOREACH(ConfigurationWidgetInterface *page, pages) {
0188         page->save(*m_settings);
0189     }
0190 
0191     m_settings->sync();
0192 #ifndef Q_OS_WIN
0193     settingsFile.setPermissions(QFile::ReadUser | QFile::WriteUser);
0194 #endif
0195 }
0196 
0197 void SettingsDialog::slotAccept()
0198 {
0199     disconnect(sender(), SIGNAL(saved()), this, SLOT(slotAccept())); // new-signal-slot: we're abusing the type system a bit here, cannot use the new syntax
0200     if (--m_saveSignalCount > 0) {
0201         return;
0202     }
0203 
0204     QStringList passwordFailures;
0205     Q_FOREACH(ConfigurationWidgetInterface *page, pages) {
0206         QString message;
0207         if (page->passwordFailures(message)) {
0208             passwordFailures << message;
0209         }
0210     }
0211     if (!passwordFailures.isEmpty()) {
0212         Gui::Util::messageBoxWarning(this, tr("Saving passwords failed"),
0213                                      tr("<p>Couldn't save passwords. These were the error messages:</p>\n<p>%1</p>")
0214                                      .arg(passwordFailures.join(QStringLiteral("<br/>"))));
0215     }
0216 
0217     buttons->setEnabled(true);
0218     QDialog::accept();
0219 }
0220 
0221 void SettingsDialog::reject()
0222 {
0223     // The changes were performed on the live data, so we have to make sure they are discarded when user cancels
0224 #define HANDLE_PLUGIN(LOWERCASE, UPPERCASE) \
0225     if (!m_original##UPPERCASE##Plugin.isEmpty() && pluginManager()->LOWERCASE##Plugin() != m_original##UPPERCASE##Plugin) { \
0226         pluginManager()->set##UPPERCASE##Plugin(m_original##UPPERCASE##Plugin); \
0227     }
0228     HANDLE_PLUGIN(addressbook, Addressbook)
0229     HANDLE_PLUGIN(password, Password)
0230     HANDLE_PLUGIN(spellchecker, Spellchecker)
0231 #undef HANDLE_PLUGIN
0232     m_senderIdentities->loadFromSettings(*m_settings);
0233     m_favoriteTags->loadFromSettings(*m_settings);
0234     QDialog::reject();
0235 }
0236 
0237 void SettingsDialog::addPage(ConfigurationWidgetInterface *page, const QString &title)
0238 {
0239     stack->addTab(page->asWidget(), title);
0240     connect(page->asWidget(), SIGNAL(widgetsUpdated()), SLOT(adjustSizeToScrollAreas())); // new-signal-slot: we're abusing the type system a bit here, cannot use the new syntax
0241     QMetaObject::invokeMethod(page->asWidget(), "updateWidgets", Qt::QueuedConnection);
0242     pages << page;
0243 }
0244 
0245 FavoriteTagsPage::FavoriteTagsPage(SettingsDialog *parent, QSettings &s, Imap::Mailbox::FavoriteTagsModel *favoriteTagsModel):
0246     QScrollArea(parent), Ui_FavoriteTagsPage(), m_favoriteTagsModel(favoriteTagsModel), m_parent(parent)
0247 {
0248     Ui_FavoriteTagsPage::setupUi(this);
0249     Q_ASSERT(m_favoriteTagsModel);
0250     moveUpButton->setIcon(UiUtils::loadIcon(QStringLiteral("go-up")));
0251     moveDownButton->setIcon(UiUtils::loadIcon(QStringLiteral("go-down")));
0252     tagTableView->setModel(m_favoriteTagsModel);
0253     tagTableView->setItemDelegate(new ColoredItemDelegate(this));
0254     tagTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
0255     tagTableView->setSelectionMode(QAbstractItemView::SingleSelection);
0256     tagTableView->setGridStyle(Qt::NoPen);
0257     tagTableView->resizeRowsToContents();
0258     tagTableView->horizontalHeader()->setStretchLastSection(true);
0259     // show tag name in color instead
0260     tagTableView->hideColumn(Imap::Mailbox::FavoriteTagsModel::COLUMN_COLOR);
0261 
0262     connect(tagTableView, &QAbstractItemView::clicked, this, &FavoriteTagsPage::updateWidgets);
0263     connect(tagTableView, &QAbstractItemView::doubleClicked, this, &FavoriteTagsPage::editButtonClicked);
0264     connect(m_favoriteTagsModel, &QAbstractItemModel::modelReset, this, &FavoriteTagsPage::updateWidgets);
0265     connect(m_favoriteTagsModel, &QAbstractItemModel::rowsInserted, this, &FavoriteTagsPage::updateWidgets);
0266     connect(m_favoriteTagsModel, &QAbstractItemModel::rowsRemoved, this, &FavoriteTagsPage::updateWidgets);
0267     connect(m_favoriteTagsModel, &QAbstractItemModel::dataChanged, this, &FavoriteTagsPage::updateWidgets);
0268     connect(moveUpButton, &QAbstractButton::clicked, this, [this](){ FavoriteTagsPage::moveTagBy(-1); });
0269     connect(moveDownButton, &QAbstractButton::clicked, this, [this](){ FavoriteTagsPage::moveTagBy(1); });
0270     connect(addButton, &QAbstractButton::clicked, this, &FavoriteTagsPage::addButtonClicked);
0271     connect(editButton, &QAbstractButton::clicked, this, &FavoriteTagsPage::editButtonClicked);
0272     connect(deleteButton, &QAbstractButton::clicked, this, &FavoriteTagsPage::deleteButtonClicked);
0273 
0274     updateWidgets();
0275 }
0276 
0277 void FavoriteTagsPage::updateWidgets()
0278 {
0279     bool enabled = tagTableView->currentIndex().isValid();
0280     deleteButton->setEnabled(enabled);
0281     editButton->setEnabled(enabled);
0282     bool upEnabled = m_favoriteTagsModel->rowCount() > 0 && tagTableView->currentIndex().row() > 0;
0283     bool downEnabled = m_favoriteTagsModel->rowCount() > 0 && tagTableView->currentIndex().isValid() &&
0284             tagTableView->currentIndex().row() < m_favoriteTagsModel->rowCount() - 1;
0285     moveUpButton->setEnabled(upEnabled);
0286     moveDownButton->setEnabled(downEnabled);
0287 
0288     tagTableView->resizeColumnToContents(Imap::Mailbox::FavoriteTagsModel::COLUMN_INDEX);
0289     tagTableView->resizeColumnToContents(Imap::Mailbox::FavoriteTagsModel::COLUMN_NAME);
0290 
0291     emit widgetsUpdated();
0292 }
0293 
0294 void FavoriteTagsPage::moveTagBy(const int offset)
0295 {
0296     int from = tagTableView->currentIndex().row();
0297     int to = tagTableView->currentIndex().row() + offset;
0298 
0299     m_favoriteTagsModel->moveTag(from, to);
0300     updateWidgets();
0301 }
0302 
0303 void FavoriteTagsPage::addButtonClicked()
0304 {
0305     m_favoriteTagsModel->appendTag(Imap::Mailbox::ItemFavoriteTagItem());
0306     tagTableView->setCurrentIndex(m_favoriteTagsModel->index(m_favoriteTagsModel->rowCount() - 1, 0));
0307     EditFavoriteTag *dialog = new EditFavoriteTag(this, m_favoriteTagsModel, tagTableView->currentIndex());
0308     dialog->setDeleteOnReject();
0309     dialog->setWindowTitle(tr("Add New Tag"));
0310     dialog->show();
0311     updateWidgets();
0312 }
0313 
0314 void FavoriteTagsPage::editButtonClicked()
0315 {
0316     EditFavoriteTag *dialog = new EditFavoriteTag(this, m_favoriteTagsModel, tagTableView->currentIndex());
0317     dialog->setWindowTitle(tr("Edit Tag"));
0318     dialog->show();
0319 }
0320 
0321 void FavoriteTagsPage::deleteButtonClicked()
0322 {
0323     Q_ASSERT(tagTableView->currentIndex().isValid());
0324     m_favoriteTagsModel->removeTagAt(tagTableView->currentIndex().row());
0325     updateWidgets();
0326 }
0327 
0328 void FavoriteTagsPage::save(QSettings &s)
0329 {
0330     m_favoriteTagsModel->saveToSettings(s);
0331 
0332     emit saved();
0333 }
0334 
0335 QWidget *FavoriteTagsPage::asWidget()
0336 {
0337     return this;
0338 }
0339 
0340 bool FavoriteTagsPage::checkValidity() const
0341 {
0342     return true;
0343 }
0344 
0345 bool FavoriteTagsPage::passwordFailures(QString &message) const
0346 {
0347     Q_UNUSED(message);
0348     return false;
0349 }
0350 
0351 GeneralPage::GeneralPage(SettingsDialog *parent, QSettings &s, Composer::SenderIdentitiesModel *identitiesModel):
0352     QScrollArea(parent), Ui_GeneralPage(), m_identitiesModel(identitiesModel), m_parent(parent)
0353 {
0354     Ui_GeneralPage::setupUi(this);
0355     Q_ASSERT(m_identitiesModel);
0356     editButton->setEnabled(false);
0357     deleteButton->setEnabled(false);
0358     moveUpButton->setIcon(UiUtils::loadIcon(QStringLiteral("go-up")));
0359     moveDownButton->setIcon(UiUtils::loadIcon(QStringLiteral("go-down")));
0360     moveUpButton->setEnabled(false);
0361     moveDownButton->setEnabled(false);
0362     identityTabelView->setModel(m_identitiesModel);
0363     identityTabelView->setSelectionBehavior(QAbstractItemView::SelectRows);
0364     identityTabelView->setSelectionMode(QAbstractItemView::SingleSelection);
0365     identityTabelView->setGridStyle(Qt::NoPen);
0366     identityTabelView->hideColumn(Composer::SenderIdentitiesModel::COLUMN_ORGANIZATION);
0367     identityTabelView->setColumnHidden(Composer::SenderIdentitiesModel::COLUMN_SIGNATURE, true);
0368     identityTabelView->resizeColumnToContents(Composer::SenderIdentitiesModel::COLUMN_NAME);
0369     identityTabelView->resizeRowsToContents();
0370     identityTabelView->horizontalHeader()->setStretchLastSection(true);
0371 
0372     Plugins::PluginManager *pluginManager = parent->pluginManager();
0373     QMap<QString, QString>::const_iterator it;
0374     int i;
0375 
0376 #define HANDLE_PLUGIN(LOWERCASE, UPPERCASE, DISABLE, NOTFOUND) \
0377     const QMap<QString, QString> &LOWERCASE##Plugins = pluginManager->available##UPPERCASE##Plugins(); \
0378     const QString &LOWERCASE##Plugin = pluginManager->LOWERCASE##Plugin(); \
0379     int LOWERCASE##Index = -1; \
0380     \
0381     for (it = LOWERCASE##Plugins.constBegin(), i = 0; it != LOWERCASE##Plugins.constEnd(); ++it, ++i) { \
0382         LOWERCASE##Box->addItem(it.value(), it.key()); \
0383         if (LOWERCASE##Index < 0 && LOWERCASE##Plugin == it.key()) \
0384             LOWERCASE##Index = i; \
0385     } \
0386     \
0387     LOWERCASE##Box->addItem(DISABLE); \
0388     \
0389     if (LOWERCASE##Plugin == QLatin1String("none")) \
0390         LOWERCASE##Index = LOWERCASE##Box->count()-1; \
0391     \
0392     if (LOWERCASE##Index == -1) { \
0393         if (!LOWERCASE##Plugin.isEmpty()) \
0394             LOWERCASE##Box->addItem(NOTFOUND.arg(LOWERCASE##Plugin), LOWERCASE##Plugin); \
0395         LOWERCASE##Index = LOWERCASE##Box->count()-1; \
0396     } \
0397     \
0398     LOWERCASE##Box->setCurrentIndex(LOWERCASE##Index);
0399 
0400     QString pluginNotFound = tr("Plugin not found (%1)");
0401     HANDLE_PLUGIN(addressbook, Addressbook, tr("Disable address book"), pluginNotFound)
0402     HANDLE_PLUGIN(password, Password, tr("Disable passwords"), pluginNotFound)
0403     HANDLE_PLUGIN(spellchecker, Spellchecker, tr("Disable spell checking"), pluginNotFound)
0404 #undef HANDLE_PLUGIN
0405 
0406     m_parent->setOriginalPlugins(passwordPlugin, addressbookPlugin, spellcheckerPlugin);
0407 
0408 
0409     markReadCheckbox->setChecked(s.value(Common::SettingsNames::autoMarkReadEnabled, QVariant(true)).toBool());
0410     markReadSeconds->setValue(s.value(Common::SettingsNames::autoMarkReadSeconds, QVariant(0)).toUInt());
0411     connect(markReadCheckbox, &QAbstractButton::toggled, markReadSeconds, &QWidget::setEnabled);
0412 
0413     auto mboxDropAction = s.value(Common::SettingsNames::mboxDropAction, QVariant(QStringLiteral("ask"))).toString();
0414 
0415     connect(mboxDropActionCheckbox, &QAbstractButton::toggled, mboxDropActionBox, &QWidget::setEnabled);
0416     if (mboxDropAction != QStringLiteral("ask"))
0417         mboxDropActionCheckbox->setChecked(true);
0418 
0419     mboxDropActionBox->addItem(tr("Move"), QStringLiteral("move"));
0420     if (mboxDropAction == QStringLiteral("move"))
0421         mboxDropActionBox->setCurrentIndex(mboxDropActionBox->count() - 1);
0422     mboxDropActionBox->addItem(tr("Copy"), QStringLiteral("copy"));
0423     if (mboxDropAction == QStringLiteral("copy"))
0424         mboxDropActionBox->setCurrentIndex(mboxDropActionBox->count() - 1);
0425 
0426     showHomepageCheckbox->setChecked(s.value(Common::SettingsNames::appLoadHomepage, QVariant(true)).toBool());
0427     showHomepageCheckbox->setToolTip(tr("<p>If enabled, Trojitá will show its homepage upon startup.</p>"
0428                                         "<p>The remote server will receive the user's IP address and versions of Trojitá, the Qt library, "
0429                                         "and the underlying operating system. No private information, like account settings "
0430                                         "or IMAP server details, are collected.</p>"));
0431 
0432     guiSystrayCheckbox->setChecked(s.value(Common::SettingsNames::guiShowSystray, QVariant(true)).toBool());
0433     guiStartMinimizedCheckbox->setChecked(s.value(Common::SettingsNames::guiStartMinimized, QVariant(false)).toBool());
0434 
0435     preferPlaintextCheckbox->setChecked(s.value(Common::SettingsNames::guiPreferPlaintextRendering).toBool());
0436     revealTrojitaVersions->setChecked(s.value(Common::SettingsNames::interopRevealVersions, QVariant(true)).toBool());
0437 
0438     connect(identityTabelView, &QAbstractItemView::clicked, this, &GeneralPage::updateWidgets);
0439     connect(identityTabelView, &QAbstractItemView::doubleClicked, this, &GeneralPage::editButtonClicked);
0440     connect(m_identitiesModel, &QAbstractItemModel::layoutChanged, this, &GeneralPage::updateWidgets);
0441     connect(m_identitiesModel, &QAbstractItemModel::rowsInserted, this, &GeneralPage::updateWidgets);
0442     connect(m_identitiesModel, &QAbstractItemModel::rowsRemoved, this, &GeneralPage::updateWidgets);
0443     connect(m_identitiesModel, &QAbstractItemModel::dataChanged, this, &GeneralPage::updateWidgets);
0444     connect(moveUpButton, &QAbstractButton::clicked, this, &GeneralPage::moveIdentityUp);
0445     connect(moveDownButton, &QAbstractButton::clicked, this, &GeneralPage::moveIdentityDown);
0446     connect(addButton, &QAbstractButton::clicked, this, &GeneralPage::addButtonClicked);
0447     connect(editButton, &QAbstractButton::clicked, this, &GeneralPage::editButtonClicked);
0448     connect(deleteButton, &QAbstractButton::clicked, this, &GeneralPage::deleteButtonClicked);
0449     connect(passwordBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &GeneralPage::passwordPluginChanged);
0450 
0451     connect(this, &GeneralPage::reloadPasswords, m_parent, &SettingsDialog::reloadPasswordsRequested);
0452 
0453     updateWidgets();
0454 }
0455 
0456 void GeneralPage::passwordPluginChanged()
0457 {
0458     const QString &passwordPlugin = m_parent->pluginManager()->passwordPlugin();
0459     const QString &selectedPasswordPlugin = passwordBox->itemData(passwordBox->currentIndex()).toString();
0460 
0461     if (selectedPasswordPlugin != passwordPlugin) {
0462         m_parent->pluginManager()->setPasswordPlugin(selectedPasswordPlugin);
0463         emit reloadPasswords();
0464     }
0465 }
0466 
0467 void GeneralPage::updateWidgets()
0468 {
0469     bool enabled = identityTabelView->currentIndex().isValid();
0470     deleteButton->setEnabled(enabled);
0471     editButton->setEnabled(enabled);
0472     bool upEnabled = m_identitiesModel->rowCount() > 0 && identityTabelView->currentIndex().row() > 0;
0473     bool downEnabled = m_identitiesModel->rowCount() > 0 && identityTabelView->currentIndex().isValid() &&
0474             identityTabelView->currentIndex().row() < m_identitiesModel->rowCount() - 1;
0475     moveUpButton->setEnabled(upEnabled);
0476     moveDownButton->setEnabled(downEnabled);
0477 
0478     identityTabelView->resizeColumnToContents(Composer::SenderIdentitiesModel::COLUMN_NAME);
0479 
0480     emit widgetsUpdated();
0481 }
0482 
0483 void GeneralPage::moveIdentityUp()
0484 {
0485     int from = identityTabelView->currentIndex().row();
0486     int to = identityTabelView->currentIndex().row() - 1;
0487 
0488     m_identitiesModel->moveIdentity(from, to);
0489     updateWidgets();
0490 }
0491 
0492 void GeneralPage::moveIdentityDown()
0493 {
0494     int from = identityTabelView->currentIndex().row();
0495     int to = identityTabelView->currentIndex().row() + 1;
0496 
0497     m_identitiesModel->moveIdentity(from, to);
0498     updateWidgets();
0499 }
0500 
0501 void GeneralPage::addButtonClicked()
0502 {
0503     m_identitiesModel->appendIdentity(Composer::ItemSenderIdentity());
0504     identityTabelView->setCurrentIndex(m_identitiesModel->index(m_identitiesModel->rowCount() - 1, 0));
0505     EditIdentity *dialog = new EditIdentity(this, m_identitiesModel, identityTabelView->currentIndex());
0506     dialog->setDeleteOnReject();
0507     dialog->setWindowTitle(tr("Add New Identity"));
0508     dialog->show();
0509     updateWidgets();
0510 }
0511 
0512 void GeneralPage::editButtonClicked()
0513 {
0514     EditIdentity *dialog = new EditIdentity(this,  m_identitiesModel, identityTabelView->currentIndex());
0515     dialog->setWindowTitle(tr("Edit Identity"));
0516     dialog->show();
0517 }
0518 
0519 void GeneralPage::deleteButtonClicked()
0520 {
0521     Q_ASSERT(identityTabelView->currentIndex().isValid());
0522     QMessageBox::StandardButton answer =
0523             QMessageBox::question(this, tr("Delete Identity?"),
0524                                   tr("Are you sure you want to delete identity %1 <%2>?").arg(
0525                                       m_identitiesModel->index(identityTabelView->currentIndex().row(),
0526                                                                Composer::SenderIdentitiesModel::COLUMN_NAME).data().toString(),
0527                                       m_identitiesModel->index(identityTabelView->currentIndex().row(),
0528                                                                Composer::SenderIdentitiesModel::COLUMN_EMAIL).data().toString()),
0529                                   QMessageBox::Yes | QMessageBox::No);
0530     if (answer == QMessageBox::Yes) {
0531         m_identitiesModel->removeIdentityAt(identityTabelView->currentIndex().row());
0532         updateWidgets();
0533     }
0534 }
0535 
0536 void GeneralPage::save(QSettings &s)
0537 {
0538     m_identitiesModel->saveToSettings(s);
0539     s.setValue(Common::SettingsNames::autoMarkReadEnabled, markReadCheckbox->isChecked());
0540     s.setValue(Common::SettingsNames::autoMarkReadSeconds, markReadSeconds->value());
0541     s.setValue(Common::SettingsNames::mboxDropAction,
0542             mboxDropActionCheckbox->isChecked() ? mboxDropActionBox->currentData() : QStringLiteral("ask"));
0543     s.setValue(Common::SettingsNames::appLoadHomepage, showHomepageCheckbox->isChecked());
0544     s.setValue(Common::SettingsNames::guiPreferPlaintextRendering, preferPlaintextCheckbox->isChecked());
0545     s.setValue(Common::SettingsNames::guiShowSystray, guiSystrayCheckbox->isChecked());
0546     s.setValue(Common::SettingsNames::guiStartMinimized, guiStartMinimizedCheckbox->isChecked());
0547     s.setValue(Common::SettingsNames::interopRevealVersions, revealTrojitaVersions->isChecked());
0548 
0549 #define HANDLE_PLUGIN(LOWERCASE, UPPERCASE) \
0550     const QString &LOWERCASE##Plugin = m_parent->pluginManager()->LOWERCASE##Plugin(); \
0551     const QString &selected##UPPERCASE##Plugin = LOWERCASE##Box->itemData(LOWERCASE##Box->currentIndex()).toString(); \
0552     if (selected##UPPERCASE##Plugin != LOWERCASE##Plugin) { \
0553         m_parent->pluginManager()->set##UPPERCASE##Plugin(selected##UPPERCASE##Plugin); \
0554     }
0555 
0556     HANDLE_PLUGIN(addressbook, Addressbook);
0557     HANDLE_PLUGIN(password, Password);
0558     HANDLE_PLUGIN(spellchecker, Spellchecker);
0559 #undef HANDLE_PLUGIN
0560 
0561     emit saved();
0562 }
0563 
0564 QWidget *GeneralPage::asWidget()
0565 {
0566     return this;
0567 }
0568 
0569 bool GeneralPage::checkValidity() const
0570 {
0571     if (m_identitiesModel->rowCount() < 1) {
0572         QToolTip::showText(identityTabelView->mapToGlobal(QPoint(10, identityTabelView->height() / 2)),
0573                            tr("Please define some identities here"), 0);
0574         return false;
0575     }
0576     return true;
0577 }
0578 
0579 bool GeneralPage::passwordFailures(QString &message) const
0580 {
0581     Q_UNUSED(message);
0582     return false;
0583 }
0584 
0585 EditIdentity::EditIdentity(QWidget *parent, Composer::SenderIdentitiesModel *identitiesModel, const QModelIndex &currentIndex):
0586     QDialog(parent), Ui_EditIdentity(), m_identitiesModel(identitiesModel), m_deleteOnReject(false)
0587 {
0588     Ui_EditIdentity::setupUi(this);
0589     m_mapper = new QDataWidgetMapper(this);
0590     m_mapper->setModel(m_identitiesModel);
0591     m_mapper->addMapping(realNameLineEdit, Composer::SenderIdentitiesModel::COLUMN_NAME);
0592     m_mapper->addMapping(emailLineEdit, Composer::SenderIdentitiesModel::COLUMN_EMAIL);
0593     m_mapper->addMapping(organisationLineEdit, Composer::SenderIdentitiesModel::COLUMN_ORGANIZATION);
0594     m_mapper->addMapping(signaturePlainTextEdit, Composer::SenderIdentitiesModel::COLUMN_SIGNATURE);
0595     m_mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
0596     m_mapper->setCurrentIndex(currentIndex.row());
0597     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
0598     connect(realNameLineEdit, &QLineEdit::textChanged, this, &EditIdentity::enableButton);
0599     connect(emailLineEdit, &QLineEdit::textChanged, this, &EditIdentity::enableButton);
0600     connect(organisationLineEdit, &QLineEdit::textChanged, this, &EditIdentity::enableButton);
0601     connect(signaturePlainTextEdit, &QPlainTextEdit::textChanged, this, &EditIdentity::enableButton);
0602     connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &QDialog::accept);
0603     connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, &QDialog::reject);
0604     connect(this, &QDialog::accepted, m_mapper, &QDataWidgetMapper::submit);
0605     connect(this, &QDialog::rejected, this, &EditIdentity::onReject);
0606     setModal(true);
0607     signaturePlainTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0608 }
0609 
0610 void EditIdentity::enableButton()
0611 {
0612     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
0613         !realNameLineEdit->text().isEmpty() && !emailLineEdit->text().isEmpty());
0614 }
0615 
0616 /** @short If enabled, make sure that the current row gets deleted when the dialog is rejected */
0617 void EditIdentity::setDeleteOnReject(const bool reject)
0618 {
0619     m_deleteOnReject = reject;
0620 }
0621 
0622 void EditIdentity::onReject()
0623 {
0624     if (m_deleteOnReject)
0625         m_identitiesModel->removeIdentityAt(m_mapper->currentIndex());
0626 }
0627 
0628 EditFavoriteTag::EditFavoriteTag(QWidget *parent, Imap::Mailbox::FavoriteTagsModel *favoriteTagsModel, const QModelIndex &currentIndex):
0629     QDialog(parent), Ui_EditFavoriteTag(), m_favoriteTagsModel(favoriteTagsModel), currentIndex(currentIndex), m_deleteOnReject(false)
0630 {
0631     Ui_EditFavoriteTag::setupUi(this);
0632 
0633     nameLineEdit->setText(name());
0634     setColorButtonColor(color());
0635     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
0636 
0637     connect(colorButton, &QAbstractButton::clicked, this, &EditFavoriteTag::colorButtonClick);
0638     connect(nameLineEdit, &QLineEdit::textChanged, this, &EditFavoriteTag::tryEnableButton);
0639 
0640     connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &QDialog::accept);
0641     connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, &QDialog::reject);
0642     connect(this, &QDialog::accepted, this, &EditFavoriteTag::onAccept);
0643     connect(this, &QDialog::rejected, this, &EditFavoriteTag::onReject);
0644     setModal(true);
0645 }
0646 
0647 QString EditFavoriteTag::name()
0648 {
0649     return m_favoriteTagsModel->data(m_favoriteTagsModel->index(currentIndex.row(), Imap::Mailbox::FavoriteTagsModel::COLUMN_NAME)).toString();
0650 }
0651 
0652 QString EditFavoriteTag::color()
0653 {
0654     return m_favoriteTagsModel->data(m_favoriteTagsModel->index(currentIndex.row(), Imap::Mailbox::FavoriteTagsModel::COLUMN_COLOR)).toString();
0655 }
0656 
0657 void EditFavoriteTag::setColorButtonColor(const QString color)
0658 {
0659     colorButton->setProperty("colorName", color);
0660     QPalette pal = colorButton->palette();
0661     pal.setColor(QPalette::Button, QColor(color));
0662     colorButton->setAutoFillBackground(true);
0663     colorButton->setPalette(pal);
0664     colorButton->setFlat(true);
0665     colorButton->update();
0666 }
0667 
0668 void EditFavoriteTag::colorButtonClick()
0669 {
0670     const QColor color = QColorDialog::getColor(QColor(colorButton->property("colorName").toString()), this, tr("Select tag color"));
0671     if (color.isValid()) {
0672         setColorButtonColor(color.name());
0673         tryEnableButton();
0674     }
0675 }
0676 
0677 void EditFavoriteTag::tryEnableButton()
0678 {
0679     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
0680         !nameLineEdit->text().isEmpty() && QColor(colorButton->property("colorName").toString()).isValid()
0681     );
0682 }
0683 
0684 /** @short If enabled, make sure that the current row gets deleted when the dialog is rejected */
0685 void EditFavoriteTag::setDeleteOnReject(const bool reject)
0686 {
0687     m_deleteOnReject = reject;
0688 }
0689 
0690 void EditFavoriteTag::onAccept()
0691 {
0692     m_favoriteTagsModel->setData(m_favoriteTagsModel->index(currentIndex.row(), Imap::Mailbox::FavoriteTagsModel::COLUMN_NAME),
0693             nameLineEdit->text());
0694     m_favoriteTagsModel->setData(m_favoriteTagsModel->index(currentIndex.row(), Imap::Mailbox::FavoriteTagsModel::COLUMN_COLOR),
0695             colorButton->property("colorName"));
0696 }
0697 
0698 void EditFavoriteTag::onReject()
0699 {
0700     if (m_deleteOnReject)
0701         m_favoriteTagsModel->removeTagAt(currentIndex.row());
0702 }
0703 
0704 ImapPage::ImapPage(SettingsDialog *parent, QSettings &s): QScrollArea(parent), Ui_ImapPage(), m_parent(parent)
0705 {
0706     Ui_ImapPage::setupUi(this);
0707     method->insertItem(NETWORK, tr("Network Connection"));
0708     method->insertItem(PROCESS, tr("Local Process"));
0709 
0710     encryption->insertItem(NONE, tr("No encryption"));
0711     encryption->insertItem(STARTTLS, tr("Use encryption (STARTTLS)"));
0712     encryption->insertItem(SSL, tr("Force encryption (TLS)"));
0713     using Common::SettingsNames;
0714     int defaultImapPort = Common::PORT_IMAPS;
0715 
0716     if (s.value(SettingsNames::imapMethodKey).toString() == SettingsNames::methodTCP) {
0717         method->setCurrentIndex(NETWORK);
0718 
0719         if (s.value(SettingsNames::imapStartTlsKey,true).toBool())
0720             encryption->setCurrentIndex(STARTTLS);
0721         else
0722             encryption->setCurrentIndex(NONE);
0723 
0724         defaultImapPort = Common::PORT_IMAP;
0725     } else if (s.value(SettingsNames::imapMethodKey).toString() == SettingsNames::methodSSL) {
0726         method->setCurrentIndex(NETWORK);
0727         encryption->setCurrentIndex(SSL);
0728     } else if (s.value(SettingsNames::imapMethodKey).toString() == SettingsNames::methodProcess) {
0729         method->setCurrentIndex(PROCESS);
0730     } else {
0731         // Default settings -- let's assume SSL and hope that users who just press Cancel will configure when they see
0732         // the network error...
0733         method->setCurrentIndex(NETWORK);
0734         encryption->setCurrentIndex(SSL);
0735     }
0736 
0737     imapHost->setText(s.value(SettingsNames::imapHostKey).toString());
0738     imapPort->setText(s.value(SettingsNames::imapPortKey, QString::number(defaultImapPort)).toString());
0739     imapPort->setValidator(new QIntValidator(1, 65535, this));
0740     connect(imapPort, &QLineEdit::textChanged, this, &ImapPage::maybeShowPortWarning);
0741     connect(encryption, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ImapPage::maybeShowPortWarning);
0742     connect(method, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ImapPage::maybeShowPortWarning);
0743     connect(encryption, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ImapPage::changePort);
0744     portWarning->setStyleSheet(SettingsDialog::warningStyleSheet);
0745     connect(imapPass, &QLineEdit::textChanged, this, &ImapPage::updateWidgets);
0746     imapUser->setText(s.value(SettingsNames::imapUserKey).toString());
0747     processPath->setText(s.value(SettingsNames::imapProcessKey).toString());
0748 
0749     imapCapabilitiesBlacklist->setText(s.value(SettingsNames::imapBlacklistedCapabilities).toStringList().join(QStringLiteral(" ")));
0750     imapUseSystemProxy->setChecked(s.value(SettingsNames::imapUseSystemProxy, true).toBool());
0751     imapNeedsNetwork->setChecked(s.value(SettingsNames::imapNeedsNetwork, true).toBool());
0752     imapIdleRenewal->setValue(s.value(SettingsNames::imapIdleRenewal, QVariant(29)).toInt());
0753     imapNumberRefreshInterval->setValue(m_parent->imapAccess()->numberRefreshInterval());
0754     accountIcon->setText(s.value(SettingsNames::imapAccountIcon).toString());
0755     archiveFolderName->setText(s.value(SettingsNames::imapArchiveFolderName).toString().isEmpty() ?
0756         SettingsNames::imapDefaultArchiveFolderName : s.value(SettingsNames::imapArchiveFolderName).toString());
0757 
0758     m_imapPort = s.value(SettingsNames::imapPortKey, QString::number(defaultImapPort)).value<quint16>();
0759 
0760     connect(method, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ImapPage::updateWidgets);
0761 
0762     // FIXME: use another account-id
0763     m_pwWatcher = m_parent->imapAccess()->passwordWatcher();
0764     connect(m_pwWatcher, &UiUtils::PasswordWatcher::stateChanged, this, &ImapPage::updateWidgets);
0765     connect(m_pwWatcher, &UiUtils::PasswordWatcher::savingFailed, this, &ImapPage::saved);
0766     connect(m_pwWatcher, &UiUtils::PasswordWatcher::savingDone, this, &ImapPage::saved);
0767     connect(m_pwWatcher, &UiUtils::PasswordWatcher::readingDone, this, &ImapPage::slotSetPassword);
0768     connect(m_parent, &SettingsDialog::reloadPasswordsRequested, imapPass, &QLineEdit::clear);
0769     connect(m_parent, &SettingsDialog::reloadPasswordsRequested, m_pwWatcher, &UiUtils::PasswordWatcher::reloadPassword);
0770 
0771     updateWidgets();
0772     maybeShowPortWarning();
0773 }
0774 
0775 void ImapPage::slotSetPassword()
0776 {
0777     imapPass->setText(m_pwWatcher->password());
0778 }
0779 
0780 void ImapPage::changePort()
0781 {
0782     imapPort->setText(QString::number(encryption->currentIndex() == SSL ? Common::PORT_IMAPS : Common::PORT_IMAP));
0783 }
0784 
0785 void ImapPage::updateWidgets()
0786 {
0787     QFormLayout *lay = formLayout;
0788     Q_ASSERT(lay);
0789 
0790     switch (method->currentIndex()) {
0791     case NETWORK:
0792         imapHost->setVisible(true);
0793         imapPort->setVisible(true);
0794         encryption->setVisible(true);
0795         lay->labelForField(imapHost)->setVisible(true);
0796         lay->labelForField(imapPort)->setVisible(true);
0797         lay->labelForField(encryption)->setVisible(true);
0798         processPath->setVisible(false);
0799         lay->labelForField(processPath)->setVisible(false);
0800         imapUseSystemProxy->setVisible(true);
0801         lay->labelForField(imapUseSystemProxy)->setVisible(true);
0802         // the "needs network" can very well apply to accounts using "local process" via SSH, so it is not disabled here
0803         break;
0804     default:
0805         imapHost->setVisible(false);
0806         imapPort->setVisible(false);
0807         encryption->setVisible(false);
0808         lay->labelForField(imapHost)->setVisible(false);
0809         lay->labelForField(imapPort)->setVisible(false);
0810         lay->labelForField(encryption)->setVisible(false);
0811         processPath->setVisible(true);
0812         lay->labelForField(processPath)->setVisible(true);
0813         imapUseSystemProxy->setVisible(false);
0814         lay->labelForField(imapUseSystemProxy)->setVisible(false);
0815     }
0816 
0817     switch (encryption->currentIndex()) {
0818     case NONE:
0819     case STARTTLS:
0820         if (imapPort->text().isEmpty() || imapPort->text() == QString::number(Common::PORT_IMAPS))
0821             imapPort->setText(QString::number(Common::PORT_IMAP));
0822         break;
0823     default:
0824         if (imapPort->text().isEmpty() || imapPort->text() == QString::number(Common::PORT_IMAP))
0825             imapPort->setText(QString::number(Common::PORT_IMAPS));
0826     }
0827 
0828     if (!m_pwWatcher->isPluginAvailable())
0829         imapPass->setText(QString());
0830 
0831     passwordWarning->setVisible(!imapPass->text().isEmpty());
0832     if (m_pwWatcher->isStorageEncrypted()) {
0833         passwordWarning->setStyleSheet(QString());
0834         passwordWarning->setText(tr("This password will be saved in encrypted storage. "
0835             "If you do not enter password here, Trojitá will prompt for one when needed."));
0836     } else {
0837         passwordWarning->setStyleSheet(SettingsDialog::warningStyleSheet);
0838         passwordWarning->setText(tr("This password will be saved in clear text. "
0839             "If you do not enter password here, Trojitá will prompt for one when needed."));
0840     }
0841 
0842     passwordPluginStatus->setVisible(!m_pwWatcher->isPluginAvailable() || m_pwWatcher->isWaitingForPlugin() || !m_pwWatcher->didReadOk() || !m_pwWatcher->didWriteOk());
0843     passwordPluginStatus->setText(m_pwWatcher->progressMessage());
0844 
0845     imapPass->setEnabled(m_pwWatcher->isPluginAvailable() && !m_pwWatcher->isWaitingForPlugin());
0846     imapPassLabel->setEnabled(m_pwWatcher->isPluginAvailable() && !m_pwWatcher->isWaitingForPlugin());
0847 
0848     emit widgetsUpdated();
0849 }
0850 
0851 void ImapPage::save(QSettings &s)
0852 {
0853     using Common::SettingsNames;
0854     if (s.value(SettingsNames::imapHostKey) != imapHost->text()) {
0855         s.remove(Common::SettingsNames::imapSslPemPubKey);
0856     }
0857     switch (method->currentIndex()) {
0858     case NETWORK:
0859         if (imapHost->text().isEmpty()) {
0860             s.remove(SettingsNames::imapMethodKey);
0861         } else if (encryption->currentIndex() == NONE){
0862             s.setValue(SettingsNames::imapMethodKey, SettingsNames::methodTCP);
0863             s.setValue(SettingsNames::imapStartTlsKey, false);
0864         } else if (encryption->currentIndex() == STARTTLS){
0865             s.setValue(SettingsNames::imapMethodKey, SettingsNames::methodTCP);
0866             s.setValue(SettingsNames::imapStartTlsKey, true);
0867         } else {
0868             s.setValue(SettingsNames::imapMethodKey, SettingsNames::methodSSL);
0869             s.setValue(SettingsNames::imapStartTlsKey, true);
0870         }
0871         s.setValue(SettingsNames::imapHostKey, imapHost->text());
0872         s.setValue(SettingsNames::imapPortKey, imapPort->text());
0873         s.setValue(SettingsNames::imapUseSystemProxy, imapUseSystemProxy->isChecked());
0874         break;
0875     default:
0876         if (processPath->text().isEmpty()) {
0877             s.remove(SettingsNames::imapMethodKey);
0878         } else {
0879             s.setValue(SettingsNames::imapMethodKey, SettingsNames::methodProcess);
0880         }
0881         s.setValue(SettingsNames::imapProcessKey, processPath->text());
0882     }
0883     s.setValue(SettingsNames::imapUserKey, imapUser->text());
0884     s.setValue(SettingsNames::imapBlacklistedCapabilities, imapCapabilitiesBlacklist->text().split(QStringLiteral(" ")));
0885     s.setValue(SettingsNames::imapNeedsNetwork, imapNeedsNetwork->isChecked());
0886     s.setValue(SettingsNames::imapIdleRenewal, imapIdleRenewal->value());
0887     m_parent->imapAccess()->setNumberRefreshInterval(imapNumberRefreshInterval->value());
0888 
0889     s.setValue(SettingsNames::imapAccountIcon, accountIcon->text().isEmpty() ? QVariant() : QVariant(accountIcon->text()));
0890     s.setValue(SettingsNames::imapArchiveFolderName, archiveFolderName->text());
0891 
0892     if (m_pwWatcher->isPluginAvailable() && !m_pwWatcher->isWaitingForPlugin()) {
0893         m_pwWatcher->setPassword(imapPass->text());
0894     } else {
0895         emit saved();
0896     }
0897 }
0898 
0899 QWidget *ImapPage::asWidget()
0900 {
0901     return this;
0902 }
0903 
0904 bool ImapPage::checkValidity() const
0905 {
0906     switch (method->currentIndex()) {
0907     case NETWORK:
0908         // We don't require the username, and that's on purpose. Some servers *could* possibly support PREAUTH :)
0909         if (checkProblemWithEmptyTextField(imapHost, tr("The IMAP server hostname is missing here")))
0910             return false;
0911         break;
0912     default:
0913         // PREAUTH must definitely be supported here -- think imap-over-ssh-with-ssh-keys etc.
0914         if (checkProblemWithEmptyTextField(processPath,
0915                                tr("The command line to the IMAP server is missing here. Perhaps you need to use SSL or TCP?"))) {
0916             return false;
0917         }
0918         break;
0919     }
0920     return true;
0921 }
0922 
0923 void ImapPage::maybeShowPortWarning()
0924 {
0925     if (method->currentIndex() == PROCESS) {
0926         portWarning->setVisible(false);
0927         return;
0928     }
0929 
0930     if (encryption->currentIndex() == SSL) {
0931         portWarning->setVisible(imapPort->text() != QString::number(Common::PORT_IMAPS));
0932         portWarning->setText(tr("This port is nonstandard. The default port for IMAP secured over SSL/TLS is %1.").arg(Common::PORT_IMAPS));
0933     } else {
0934         portWarning->setVisible(imapPort->text() != QString::number(Common::PORT_IMAP));
0935         if (encryption->currentIndex() == STARTTLS) {
0936             portWarning->setText(tr("This port is nonstandard. The default port for IMAP secured via STARTTLS is %1.").arg(Common::PORT_IMAP));
0937         } else {
0938             portWarning->setText(tr("This port is nonstandard. The default port for IMAP over cleartext is %1.").arg(Common::PORT_IMAP));
0939         }
0940     }
0941 }
0942 
0943 bool ImapPage::passwordFailures(QString &message) const
0944 {
0945     if (!m_pwWatcher->isPluginAvailable() || m_pwWatcher->isWaitingForPlugin() || m_pwWatcher->didWriteOk()) {
0946         return false;
0947     } else {
0948         message = m_pwWatcher->progressMessage();
0949         return true;
0950     }
0951 }
0952 
0953 
0954 CachePage::CachePage(QWidget *parent, QSettings &s): QScrollArea(parent), Ui_CachePage()
0955 {
0956     Ui_CachePage::setupUi(this);
0957 
0958     using Common::SettingsNames;
0959 
0960     QString val = s.value(SettingsNames::cacheOfflineKey).toString();
0961     if (val == SettingsNames::cacheOfflineAll) {
0962         offlineEverything->setChecked(true);
0963     } else if (val == SettingsNames::cacheOfflineNone) {
0964         offlineNope->setChecked(true);
0965     } else {
0966         offlineXDays->setChecked(true);
0967     }
0968 
0969     offlineNumberOfDays->setValue(s.value(SettingsNames::cacheOfflineNumberDaysKey, QVariant(30)).toInt());
0970 
0971     val = s.value(SettingsNames::watchedFoldersKey).toString();
0972     if (val == Common::SettingsNames::watchAll) {
0973         watchAll->setChecked(true);
0974     } else if (val == Common::SettingsNames::watchSubscribed) {
0975         watchSubscribed->setChecked(true);
0976     } else {
0977         watchInbox->setChecked(true);
0978     }
0979 
0980     updateWidgets();
0981 
0982     connect(offlineNope, &QAbstractButton::clicked, this, &CachePage::updateWidgets);
0983     connect(offlineXDays, &QAbstractButton::clicked, this, &CachePage::updateWidgets);
0984     connect(offlineEverything, &QAbstractButton::clicked, this, &CachePage::updateWidgets);
0985 }
0986 
0987 void CachePage::updateWidgets()
0988 {
0989     offlineNumberOfDays->setEnabled(offlineXDays->isChecked());
0990     emit widgetsUpdated();
0991 }
0992 
0993 void CachePage::save(QSettings &s)
0994 {
0995     using Common::SettingsNames;
0996 
0997     if (offlineEverything->isChecked())
0998         s.setValue(SettingsNames::cacheOfflineKey, SettingsNames::cacheOfflineAll);
0999     else if (offlineXDays->isChecked())
1000         s.setValue(SettingsNames::cacheOfflineKey, SettingsNames::cacheOfflineXDays);
1001     else
1002         s.setValue(SettingsNames::cacheOfflineKey, SettingsNames::cacheOfflineNone);
1003 
1004     s.setValue(SettingsNames::cacheOfflineNumberDaysKey, offlineNumberOfDays->value());
1005 
1006     if (watchAll->isChecked()) {
1007         s.setValue(SettingsNames::watchedFoldersKey, SettingsNames::watchAll);
1008     } else if (watchSubscribed->isChecked()) {
1009         s.setValue(SettingsNames::watchedFoldersKey, SettingsNames::watchSubscribed);
1010     } else {
1011         s.setValue(SettingsNames::watchedFoldersKey, SettingsNames::watchOnlyInbox);
1012     }
1013 
1014     emit saved();
1015 }
1016 
1017 QWidget *CachePage::asWidget()
1018 {
1019     return this;
1020 }
1021 
1022 bool CachePage::checkValidity() const
1023 {
1024     // Nothing really special for this class
1025     return true;
1026 }
1027 
1028 bool CachePage::passwordFailures(QString &message) const
1029 {
1030     Q_UNUSED(message);
1031     return false;
1032 }
1033 
1034 OutgoingPage::OutgoingPage(SettingsDialog *parent, QSettings &s): QScrollArea(parent), Ui_OutgoingPage(), m_parent(parent)
1035 {
1036     using Common::SettingsNames;
1037     Ui_OutgoingPage::setupUi(this);
1038     // FIXME: use another account-id at some point in future
1039     //        we are now using the profile to avoid overwriting passwords of
1040     //        other profiles in secure storage
1041     QString profileName = QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
1042     m_smtpAccountSettings = new MSA::Account(this, &s, profileName);
1043 
1044     portWarningLabel->setStyleSheet(SettingsDialog::warningStyleSheet);
1045 
1046     method->insertItem(NETWORK, tr("Network"));
1047     method->insertItem(SENDMAIL, tr("Local sendmail-compatible"));
1048     method->insertItem(IMAP_SENDMAIL, tr("IMAP SENDMAIL Extension"));;
1049 
1050     encryption->insertItem(SMTP, tr("No encryption"));
1051     encryption->insertItem(SMTP_STARTTLS, tr("Use encryption (STARTTLS)"));
1052     encryption->insertItem(SSMTP, tr("Force encryption (TLS)"));
1053     encryption->setCurrentIndex(SSMTP);
1054 
1055     connect(method, static_cast<void (QComboBox::*)(const int)>(&QComboBox::currentIndexChanged), this, &OutgoingPage::slotSetSubmissionMethod);
1056     connect(encryption, static_cast<void (QComboBox::*)(const int)>(&QComboBox::currentIndexChanged), this, &OutgoingPage::slotSetSubmissionMethod);
1057 
1058     connect(m_smtpAccountSettings, &MSA::Account::submissionMethodChanged, this, &OutgoingPage::updateWidgets);
1059     connect(m_smtpAccountSettings, &MSA::Account::saveToImapChanged, this, &OutgoingPage::updateWidgets);
1060     connect(m_smtpAccountSettings, &MSA::Account::authenticateEnabledChanged, this, &OutgoingPage::updateWidgets);
1061     connect(m_smtpAccountSettings, &MSA::Account::reuseImapAuthenticationChanged, this, &OutgoingPage::updateWidgets);
1062     connect(smtpPass, &QLineEdit::textChanged, this, &OutgoingPage::updateWidgets);
1063     connect(smtpHost, &LineEdit::textEditingFinished, m_smtpAccountSettings, &MSA::Account::setServer);
1064     connect(smtpUser, &LineEdit::textEditingFinished, m_smtpAccountSettings, &MSA::Account::setUsername);
1065     connect(smtpPort, &LineEdit::textEditingFinished, this, &OutgoingPage::setPortByText);
1066     connect(m_smtpAccountSettings, &MSA::Account::showPortWarning, this, &OutgoingPage::showPortWarning);
1067     connect(smtpAuth, &QAbstractButton::toggled, m_smtpAccountSettings, &MSA::Account::setAuthenticateEnabled);
1068     connect(smtpAuthReuseImapCreds, &QAbstractButton::toggled, m_smtpAccountSettings, &MSA::Account::setReuseImapAuthentication);
1069     connect(saveToImap, &QAbstractButton::toggled, m_smtpAccountSettings, &MSA::Account::setSaveToImap);
1070     connect(saveFolderName, &LineEdit::textEditingFinished, m_smtpAccountSettings, &MSA::Account::setSentMailboxName);
1071     connect(smtpBurl, &QAbstractButton::toggled, m_smtpAccountSettings, &MSA::Account::setUseBurl);
1072     connect(sendmail, &LineEdit::textEditingFinished, m_smtpAccountSettings, &MSA::Account::setPathToSendmail);
1073 
1074     m_pwWatcher = new UiUtils::PasswordWatcher(this, m_parent->pluginManager(),
1075                                                profileName.isEmpty() ? QStringLiteral("account-0") : profileName,
1076                                                QStringLiteral("smtp"));
1077     connect(m_pwWatcher, &UiUtils::PasswordWatcher::stateChanged, this, &OutgoingPage::updateWidgets);
1078     connect(m_pwWatcher, &UiUtils::PasswordWatcher::savingFailed, this, &OutgoingPage::saved);
1079     connect(m_pwWatcher, &UiUtils::PasswordWatcher::savingDone, this, &OutgoingPage::saved);
1080     connect(m_pwWatcher, &UiUtils::PasswordWatcher::readingDone, this, &OutgoingPage::slotSetPassword);
1081     connect(m_parent, &SettingsDialog::reloadPasswordsRequested, smtpPass, &QLineEdit::clear);
1082     connect(m_parent, &SettingsDialog::reloadPasswordsRequested, m_pwWatcher, &UiUtils::PasswordWatcher::reloadPassword);
1083 
1084     updateWidgets();
1085 }
1086 
1087 void OutgoingPage::slotSetPassword()
1088 {
1089     smtpPass->setText(m_pwWatcher->password());
1090 }
1091 
1092 void OutgoingPage::slotSetSubmissionMethod()
1093 {
1094     switch (method->currentIndex()) {
1095     case SENDMAIL:
1096         m_smtpAccountSettings->setSubmissionMethod(MSA::Account::Method::SENDMAIL);
1097         break;
1098     case IMAP_SENDMAIL:
1099         m_smtpAccountSettings->setSubmissionMethod(MSA::Account::Method::IMAP_SENDMAIL);
1100         break;
1101     case NETWORK:
1102         switch (encryption->currentIndex()) {
1103         case SMTP:
1104             m_smtpAccountSettings->setSubmissionMethod(MSA::Account::Method::SMTP);
1105             break;
1106         case SMTP_STARTTLS:
1107             m_smtpAccountSettings->setSubmissionMethod(MSA::Account::Method::SMTP_STARTTLS);
1108             break;
1109         case SSMTP:
1110             m_smtpAccountSettings->setSubmissionMethod(MSA::Account::Method::SSMTP);
1111             break;
1112         }
1113         break;
1114     default:
1115         Q_ASSERT(false);
1116     }
1117     // Toggle the default ports upon changing the delivery method
1118     smtpPort->setText(QString::number(m_smtpAccountSettings->port()));
1119 }
1120 
1121 void OutgoingPage::setPortByText(const QString &text)
1122 {
1123     m_smtpAccountSettings->setPort(text.toUShort());
1124 }
1125 
1126 void OutgoingPage::updateWidgets()
1127 {
1128     QFormLayout *lay = formLayout;
1129     Q_ASSERT(lay);
1130 
1131     switch (m_smtpAccountSettings->submissionMethod()) {
1132     case MSA::Account::Method::SMTP:
1133         method->setCurrentIndex(NETWORK);
1134         encryption->setCurrentIndex(SMTP);
1135         break;
1136     case MSA::Account::Method::SMTP_STARTTLS:
1137         method->setCurrentIndex(NETWORK);
1138         encryption->setCurrentIndex(SMTP_STARTTLS);
1139         break;
1140     case MSA::Account::Method::SSMTP:
1141         method->setCurrentIndex(NETWORK);
1142         encryption->setCurrentIndex(SSMTP);
1143         break;
1144     case MSA::Account::Method::SENDMAIL:
1145         method->setCurrentIndex(SENDMAIL);
1146         encryption->setVisible(false);
1147         encryptionLabel->setVisible(false);
1148         break;
1149     case MSA::Account::Method::IMAP_SENDMAIL:
1150         method->setCurrentIndex(IMAP_SENDMAIL);
1151         encryption->setVisible(false);
1152         encryptionLabel->setVisible(false);
1153         break;
1154     }
1155 
1156     switch (m_smtpAccountSettings->submissionMethod()) {
1157     case MSA::Account::Method::SMTP:
1158     case MSA::Account::Method::SMTP_STARTTLS:
1159     case MSA::Account::Method::SSMTP:
1160     {
1161         encryption->setVisible(true);
1162         encryptionLabel->setVisible(true);
1163         smtpHost->setVisible(true);
1164         lay->labelForField(smtpHost)->setVisible(true);
1165         smtpHost->setText(m_smtpAccountSettings->server());
1166         smtpPort->setVisible(true);
1167         lay->labelForField(smtpPort)->setVisible(true);
1168         smtpPort->setText(QString::number(m_smtpAccountSettings->port()));
1169         smtpPort->setValidator(new QIntValidator(1, 65535, this));
1170         smtpAuth->setVisible(true);
1171         lay->labelForField(smtpAuth)->setVisible(true);
1172         bool authEnabled = m_smtpAccountSettings->authenticateEnabled();
1173         smtpAuth->setChecked(authEnabled);
1174         smtpAuthReuseImapCreds->setVisible(authEnabled);
1175         lay->labelForField(smtpAuthReuseImapCreds)->setVisible(authEnabled);
1176         bool reuseImapCreds = m_smtpAccountSettings->reuseImapAuthentication();
1177         smtpAuthReuseImapCreds->setChecked(reuseImapCreds);
1178         smtpUser->setVisible(authEnabled && !reuseImapCreds);
1179         lay->labelForField(smtpUser)->setVisible(authEnabled && !reuseImapCreds);
1180         smtpUser->setText(m_smtpAccountSettings->username());
1181         sendmail->setVisible(false);
1182         lay->labelForField(sendmail)->setVisible(false);
1183         saveToImap->setVisible(true);
1184         lay->labelForField(saveToImap)->setVisible(true);
1185         saveToImap->setChecked(m_smtpAccountSettings->saveToImap());
1186         smtpBurl->setVisible(saveToImap->isChecked());
1187         lay->labelForField(smtpBurl)->setVisible(saveToImap->isChecked());
1188         smtpBurl->setChecked(m_smtpAccountSettings->useBurl());
1189 
1190         if (!m_pwWatcher->isPluginAvailable())
1191             smtpPass->setText(QString());
1192 
1193         passwordWarning->setVisible(authEnabled && !reuseImapCreds && !smtpPass->text().isEmpty());
1194         if (m_pwWatcher->isStorageEncrypted()) {
1195             passwordWarning->setStyleSheet(QString());
1196             passwordWarning->setText(tr("This password will be saved in encrypted storage. "
1197                 "If you do not enter password here, Trojitá will prompt for one when needed."));
1198         } else {
1199             passwordWarning->setStyleSheet(SettingsDialog::warningStyleSheet);
1200             passwordWarning->setText(tr("This password will be saved in clear text. "
1201                 "If you do not enter password here, Trojitá will prompt for one when needed."));
1202         }
1203 
1204         passwordPluginStatus->setVisible(authEnabled && !reuseImapCreds &&
1205                                          (!m_pwWatcher->isPluginAvailable() || m_pwWatcher->isWaitingForPlugin() || !m_pwWatcher->didReadOk() || !m_pwWatcher->didWriteOk()));
1206         passwordPluginStatus->setText(m_pwWatcher->progressMessage());
1207 
1208         smtpPass->setVisible(authEnabled && !reuseImapCreds);
1209         smtpPass->setEnabled(m_pwWatcher->isPluginAvailable() && !m_pwWatcher->isWaitingForPlugin());
1210         lay->labelForField(smtpPass)->setVisible(authEnabled && !reuseImapCreds);
1211         lay->labelForField(smtpPass)->setEnabled(m_pwWatcher->isPluginAvailable() && !m_pwWatcher->isWaitingForPlugin());
1212 
1213         break;
1214     }
1215     case MSA::Account::Method::SENDMAIL:
1216     case MSA::Account::Method::IMAP_SENDMAIL:
1217         encryption->setVisible(false);
1218         encryptionLabel->setVisible(false);
1219         smtpHost->setVisible(false);
1220         lay->labelForField(smtpHost)->setVisible(false);
1221         smtpPort->setVisible(false);
1222         lay->labelForField(smtpPort)->setVisible(false);
1223         showPortWarning(QString());
1224         smtpAuth->setVisible(false);
1225         lay->labelForField(smtpAuth)->setVisible(false);
1226         smtpUser->setVisible(false);
1227         lay->labelForField(smtpUser)->setVisible(false);
1228         smtpPass->setVisible(false);
1229         lay->labelForField(smtpPass)->setVisible(false);
1230         passwordWarning->setVisible(false);
1231         passwordPluginStatus->setVisible(false);
1232         if (m_smtpAccountSettings->submissionMethod() == MSA::Account::Method::SENDMAIL) {
1233             sendmail->setVisible(true);
1234             lay->labelForField(sendmail)->setVisible(true);
1235             sendmail->setText(m_smtpAccountSettings->pathToSendmail());
1236             if (sendmail->text().isEmpty())
1237                 sendmail->setText(Common::SettingsNames::sendmailDefaultCmd);
1238             saveToImap->setVisible(true);
1239             saveToImap->setChecked(m_smtpAccountSettings->saveToImap());
1240             lay->labelForField(saveToImap)->setVisible(true);
1241         } else {
1242             sendmail->setVisible(false);
1243             lay->labelForField(sendmail)->setVisible(false);
1244             saveToImap->setChecked(true);
1245             saveToImap->setVisible(false);
1246             lay->labelForField(saveToImap)->setVisible(false);
1247         }
1248         smtpBurl->setVisible(false);
1249         lay->labelForField(smtpBurl)->setVisible(false);
1250         smtpBurl->setChecked(m_smtpAccountSettings->useBurl());
1251         passwordPluginStatus->setVisible(false);
1252     }
1253     saveFolderName->setVisible(saveToImap->isChecked());
1254     lay->labelForField(saveFolderName)->setVisible(saveToImap->isChecked());
1255     saveFolderName->setText(m_smtpAccountSettings->sentMailboxName());
1256 
1257     emit widgetsUpdated();
1258 
1259 }
1260 
1261 void OutgoingPage::save(QSettings &s)
1262 {
1263     m_smtpAccountSettings->saveSettings();
1264 
1265     if (smtpAuth->isVisibleTo(this) && smtpAuth->isChecked() && m_pwWatcher->isPluginAvailable() && !m_pwWatcher->isWaitingForPlugin()) {
1266         m_pwWatcher->setPassword(smtpPass->text());
1267     } else {
1268         emit saved();
1269     }
1270 }
1271 
1272 void OutgoingPage::showPortWarning(const QString &warning)
1273 {
1274     if (!warning.isEmpty()) {
1275         portWarningLabel->setVisible(true);
1276         portWarningLabel->setText(warning);
1277     } else {
1278         portWarningLabel->setVisible(false);
1279     }
1280 
1281 }
1282 
1283 QWidget *OutgoingPage::asWidget()
1284 {
1285     return this;
1286 }
1287 
1288 bool OutgoingPage::checkValidity() const
1289 {
1290     switch (m_smtpAccountSettings->submissionMethod()) {
1291     case MSA::Account::Method::SMTP:
1292     case MSA::Account::Method::SMTP_STARTTLS:
1293     case MSA::Account::Method::SSMTP:
1294         if (checkProblemWithEmptyTextField(smtpHost, tr("The SMTP server hostname is missing here")))
1295             return false;
1296         if (smtpAuth->isChecked() && !smtpAuthReuseImapCreds->isChecked() && checkProblemWithEmptyTextField(smtpUser, tr("The SMTP username is missing here")))
1297             return false;
1298         break;
1299     case MSA::Account::Method::SENDMAIL:
1300         if (checkProblemWithEmptyTextField(sendmail, tr("The SMTP server hostname is missing here")))
1301             return false;
1302         break;
1303     case MSA::Account::Method::IMAP_SENDMAIL:
1304         break;
1305     }
1306 
1307     if (saveToImap->isChecked() && checkProblemWithEmptyTextField(saveFolderName, tr("Please specify the folder name here")))
1308         return false;
1309 
1310     return true;
1311 }
1312 
1313 bool OutgoingPage::passwordFailures(QString &message) const
1314 {
1315     // The const_cast is needed as Qt4 does not define the arguement of isVisibleTo as const
1316     if (!smtpAuth->isVisibleTo(const_cast<Gui::OutgoingPage*>(this)) || !smtpAuth->isChecked() || !m_pwWatcher->isPluginAvailable() || m_pwWatcher->isWaitingForPlugin() || m_pwWatcher->didWriteOk()) {
1317         return false;
1318     } else {
1319         message = m_pwWatcher->progressMessage();
1320         return true;
1321     }
1322 }
1323 
1324 }