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

0001 /* Copyright (C) 2006 - 2015 Jan Kundrát <jkt@flaska.net>
0002    Copyright (C) 2013 - 2015 Pali Rohár <pali.rohar@gmail.com>
0003 
0004    This file is part of the Trojita Qt IMAP e-mail client,
0005    http://trojita.flaska.net/
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU General Public License as
0009    published by the Free Software Foundation; either version 2 of
0010    the License or (at your option) version 3 or any later version
0011    accepted by the membership of KDE e.V. (or its successor approved
0012    by the membership of KDE e.V.), which shall act as a proxy
0013    defined in Section 14 of version 3 of the license.
0014 
0015    This program is distributed in the hope that it will be useful,
0016    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0018    GNU General Public License for more details.
0019 
0020    You should have received a copy of the GNU General Public License
0021    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0022 */
0023 
0024 #include <QAuthenticator>
0025 #include <QDesktopServices>
0026 #include <QDesktopWidget>
0027 #include <QDir>
0028 #include <QDockWidget>
0029 #include <QFileDialog>
0030 #include <QHeaderView>
0031 #include <QItemSelectionModel>
0032 #include <QKeyEvent>
0033 #include <QMenuBar>
0034 #include <QMessageBox>
0035 #include <QPainterPath>
0036 #include <QProgressBar>
0037 #include <QRegularExpression>
0038 #include <QScopedPointer>
0039 #include <QScreen>
0040 #include <QScrollBar>
0041 #include <QSplitter>
0042 #include <QSslError>
0043 #include <QSslKey>
0044 #include <QStackedWidget>
0045 #include <QStatusBar>
0046 #include <QTextDocument>
0047 #include <QToolBar>
0048 #include <QToolButton>
0049 #include <QToolTip>
0050 #include <QUrl>
0051 #include <QWheelEvent>
0052 
0053 #include "configure.cmake.h"
0054 #include "Common/Application.h"
0055 #include "Common/InvokeMethod.h"
0056 #include "Common/Paths.h"
0057 #include "Common/PortNumbers.h"
0058 #include "Common/SettingsNames.h"
0059 #include "Composer/Mailto.h"
0060 #include "Composer/SenderIdentitiesModel.h"
0061 #ifdef TROJITA_HAVE_CRYPTO_MESSAGES
0062 #  ifdef TROJITA_HAVE_GPGMEPP
0063 #    include "Cryptography/GpgMe++.h"
0064 #  endif
0065 #endif
0066 #include "Imap/Model/ImapAccess.h"
0067 #include "Imap/Model/MailboxTree.h"
0068 #include "Imap/Model/Model.h"
0069 #include "Imap/Model/ModelWatcher.h"
0070 #include "Imap/Model/MsgListModel.h"
0071 #include "Imap/Model/NetworkWatcher.h"
0072 #include "Imap/Model/PrettyMailboxModel.h"
0073 #include "Imap/Model/PrettyMsgListModel.h"
0074 #include "Imap/Model/SpecialFlagNames.h"
0075 #include "Imap/Model/ThreadingMsgListModel.h"
0076 #include "Imap/Model/FavoriteTagsModel.h"
0077 #include "Imap/Model/Utils.h"
0078 #include "Imap/Tasks/ImapTask.h"
0079 #include "Imap/Network/FileDownloadManager.h"
0080 #include "MSA/ImapSubmit.h"
0081 #include "MSA/Sendmail.h"
0082 #include "MSA/SMTP.h"
0083 #include "Plugins/AddressbookPlugin.h"
0084 #include "Plugins/PasswordPlugin.h"
0085 #include "Plugins/PluginManager.h"
0086 #include "CompleteMessageWidget.h"
0087 #include "ComposeWidget.h"
0088 #include "MailBoxTreeView.h"
0089 #include "MessageListWidget.h"
0090 #include "MessageView.h"
0091 #include "MessageSourceWidget.h"
0092 #include "Gui/MessageHeadersWidget.h"
0093 #include "MsgListView.h"
0094 #include "OnePanelAtTimeWidget.h"
0095 #include "PasswordDialog.h"
0096 #include "ProtocolLoggerWidget.h"
0097 #include "SettingsDialog.h"
0098 #include "SimplePartWidget.h"
0099 #include "Streams/SocketFactory.h"
0100 #include "TaskProgressIndicator.h"
0101 #include "Util.h"
0102 #include "Window.h"
0103 #include "ShortcutHandler/ShortcutHandler.h"
0104 
0105 #include "ui_CreateMailboxDialog.h"
0106 #include "ui_AboutDialog.h"
0107 #include "ui_ImapInfoDialog.h"
0108 
0109 #include "Imap/Model/ModelTest/modeltest.h"
0110 #include "UiUtils/IconLoader.h"
0111 #include "UiUtils/QaimDfsIterator.h"
0112 
0113 /** @short All user-facing widgets and related classes */
0114 namespace Gui
0115 {
0116 
0117     static const char * const netErrorUnseen = "net_error_unseen";
0118 
0119 MainWindow::MainWindow(QSettings *settings): QMainWindow(), m_imapAccess(0), m_mainHSplitter(0), m_mainVSplitter(0),
0120     m_mainStack(0), m_layoutMode(LAYOUT_COMPACT), m_skipSavingOfUI(true), m_delayedStateSaving(0), m_actionSortNone(0),
0121     m_ignoreStoredPassword(false), m_settings(settings), m_pluginManager(0), m_networkErrorMessageBox(0), m_trayIcon(0)
0122 {
0123     setAttribute(Qt::WA_AlwaysShowToolTips);
0124     // m_pluginManager must be created before calling createWidgets
0125     m_pluginManager = new Plugins::PluginManager(this, m_settings,
0126                                                  Common::SettingsNames::addressbookPlugin, Common::SettingsNames::passwordPlugin,
0127                                                  Common::SettingsNames::spellcheckerPlugin);
0128     connect(m_pluginManager, &Plugins::PluginManager::pluginsChanged, this, &MainWindow::slotPluginsChanged);
0129     connect(m_pluginManager, &Plugins::PluginManager::pluginError, this, [this](const QString &errorMessage) {
0130         Gui::Util::messageBoxWarning(this, tr("Plugin Error"),
0131                                      //: The %1 placeholder is a full error message as provided by Qt, ready for human consumption.
0132                                      tr("A plugin failed to load, therefore some functionality might be lost. "
0133                                             "You might want to update your system or report a bug to your vendor."
0134                                             "\n\n%1").arg(errorMessage));
0135     });
0136 #ifdef TROJITA_HAVE_CRYPTO_MESSAGES
0137     Plugins::PluginManager::MimePartReplacers replacers;
0138 #ifdef TROJITA_HAVE_GPGMEPP
0139     replacers.emplace_back(std::make_shared<Cryptography::GpgMeReplacer>());
0140 #endif
0141     m_pluginManager->setMimePartReplacers(replacers);
0142 #endif
0143 
0144     // ImapAccess contains a wrapper for retrieving passwords through some plugin.
0145     // That PasswordWatcher is used by the SettingsDialog's widgets *and* by this class,
0146     // which means that ImapAccess has to be constructed before we go and open the settings dialog.
0147 
0148     // FIXME: use another account-id at some point in future
0149     //        we are now using the profile to avoid overwriting passwords of
0150     //        other profiles in secure storage
0151     QString profileName = QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
0152     m_imapAccess = new Imap::ImapAccess(this, m_settings, m_pluginManager, profileName);
0153     connect(m_imapAccess, &Imap::ImapAccess::cacheError, this, &MainWindow::cacheError);
0154     connect(m_imapAccess, &Imap::ImapAccess::checkSslPolicy, this, &MainWindow::checkSslPolicy, Qt::QueuedConnection);
0155 
0156     ShortcutHandler *shortcutHandler = new ShortcutHandler(this);
0157     shortcutHandler->setSettingsObject(m_settings);
0158     defineActions();
0159     shortcutHandler->readSettings(); // must happen after defineActions()
0160 
0161     // must be created before calling createWidgets
0162     m_favoriteTags = new Imap::Mailbox::FavoriteTagsModel(this);
0163     m_favoriteTags->loadFromSettings(*m_settings);
0164 
0165     createWidgets();
0166 
0167     Imap::migrateSettings(m_settings);
0168 
0169     m_senderIdentities = new Composer::SenderIdentitiesModel(this);
0170     m_senderIdentities->loadFromSettings(*m_settings);
0171 
0172     if (! m_settings->contains(Common::SettingsNames::imapMethodKey)) {
0173         QTimer::singleShot(0, this, SLOT(slotShowSettings()));
0174     }
0175 
0176 
0177     setupModels();
0178     createActions();
0179     createMenus();
0180     slotToggleSysTray();
0181     slotPluginsChanged();
0182 
0183     slotFavoriteTagsChanged();
0184     connect(m_favoriteTags, &QAbstractItemModel::modelReset, this, &MainWindow::slotFavoriteTagsChanged);
0185     connect(m_favoriteTags, &QAbstractItemModel::layoutChanged, this, &MainWindow::slotFavoriteTagsChanged);
0186     connect(m_favoriteTags, &QAbstractItemModel::rowsMoved, this, &MainWindow::slotFavoriteTagsChanged);
0187     connect(m_favoriteTags, &QAbstractItemModel::rowsInserted, this, &MainWindow::slotFavoriteTagsChanged);
0188     connect(m_favoriteTags, &QAbstractItemModel::rowsRemoved, this, &MainWindow::slotFavoriteTagsChanged);
0189     connect(m_favoriteTags, &QAbstractItemModel::dataChanged, this, &MainWindow::slotFavoriteTagsChanged);
0190 
0191     // Please note that Qt 4.6.1 really requires passing the method signature this way, *not* using the SLOT() macro
0192     QDesktopServices::setUrlHandler(QStringLiteral("mailto"), this, "slotComposeMailUrl");
0193     QDesktopServices::setUrlHandler(QStringLiteral("x-trojita-manage-contact"), this, "slotManageContact");
0194 
0195     slotUpdateWindowTitle();
0196 
0197     CALL_LATER_NOARG(this, recoverDrafts);
0198 
0199     if (m_actionLayoutWide->isEnabled() &&
0200             m_settings->value(Common::SettingsNames::guiMainWindowLayout) == Common::SettingsNames::guiMainWindowLayoutWide) {
0201         m_actionLayoutWide->trigger();
0202     } else if (m_settings->value(Common::SettingsNames::guiMainWindowLayout) == Common::SettingsNames::guiMainWindowLayoutOneAtTime) {
0203         m_actionLayoutOneAtTime->trigger();
0204     } else {
0205         m_actionLayoutCompact->trigger();
0206     }
0207 
0208     connect(qApp, &QGuiApplication::applicationStateChanged, this,
0209             [&](Qt::ApplicationState state) {
0210                 if (state == Qt::ApplicationActive && m_networkErrorMessageBox && m_networkErrorMessageBox->property(netErrorUnseen).toBool()) {
0211                     m_networkErrorMessageBox->setProperty(netErrorUnseen, false);
0212                     m_networkErrorMessageBox->show();
0213                 }
0214             });
0215 
0216     // Don't listen to QDesktopWidget::resized; that is emitted too early (when it gets fired, the screen size has changed, but
0217     // the workspace area is still the old one). Instead, listen to workAreaResized which gets emitted at an appropriate time.
0218     // The delay is still there to guarantee some smoothing; on jkt's box there are typically three events in a rapid sequence
0219     // (some of them most likely due to the fact that at first, the actual desktop gets resized, the plasma panel reacts
0220     // to that and only after the panel gets resized, the available size of "the rest" is correct again).
0221     // Which is why it makes sense to introduce some delay in there. The 0.5s delay is my best guess and "should work" (especially
0222     // because every change bumps the timer anyway, as Thomas pointed out).
0223     QTimer *delayedResize = new QTimer(this);
0224     delayedResize->setSingleShot(true);
0225     delayedResize->setInterval(500);
0226     connect(delayedResize, &QTimer::timeout, this, &MainWindow::desktopGeometryChanged);
0227     connect(QGuiApplication::primaryScreen(), &QScreen::availableGeometry, delayedResize, static_cast<void (QTimer::*)()>(&QTimer::start));
0228     m_skipSavingOfUI = false;
0229 }
0230 
0231 void MainWindow::defineActions()
0232 {
0233     ShortcutHandler *shortcutHandler = ShortcutHandler::instance();
0234     shortcutHandler->defineAction(QStringLiteral("action_application_exit"), QStringLiteral("application-exit"), tr("E&xit"), QKeySequence::Quit);
0235     shortcutHandler->defineAction(QStringLiteral("action_compose_mail"), QStringLiteral("document-edit"), tr("&New Message..."), QKeySequence::New);
0236     shortcutHandler->defineAction(QStringLiteral("action_compose_draft"), QStringLiteral("document-open-recent"), tr("&Edit Draft..."));
0237     shortcutHandler->defineAction(QStringLiteral("action_show_menubar"), QStringLiteral("view-list-text"), tr("Show Main Menu &Bar"), tr("Ctrl+M"));
0238     shortcutHandler->defineAction(QStringLiteral("action_expunge"), QStringLiteral("trash-empty"), tr("Exp&unge"), tr("Ctrl+E"));
0239     shortcutHandler->defineAction(QStringLiteral("action_mark_as_read"), QStringLiteral("mail-mark-read"), tr("Mark as &Read"), QStringLiteral("M"));
0240     shortcutHandler->defineAction(QStringLiteral("action_go_to_next_unread"), QStringLiteral("arrow-right"), tr("&Next Unread Message"), QStringLiteral("N"));
0241     shortcutHandler->defineAction(QStringLiteral("action_go_to_previous_unread"), QStringLiteral("arrow-left"), tr("&Previous Unread Message"), QStringLiteral("P"));
0242     shortcutHandler->defineAction(QStringLiteral("action_mark_as_deleted"), QStringLiteral("list-remove"), tr("Mark as &Deleted"), QKeySequence(Qt::Key_Delete).toString());
0243     shortcutHandler->defineAction(QStringLiteral("action_mark_as_flagged"), QStringLiteral("mail-flagged"), tr("Mark as &Flagged"), QStringLiteral("S"));
0244     shortcutHandler->defineAction(QStringLiteral("action_mark_as_junk"), QStringLiteral("mail-mark-junk"), tr("Mark as &Junk"), QStringLiteral("J"));
0245     shortcutHandler->defineAction(QStringLiteral("action_mark_as_notjunk"), QStringLiteral("mail-mark-notjunk"), tr("Mark as Not &junk"), QStringLiteral("Shift+J"));
0246     shortcutHandler->defineAction(QStringLiteral("action_save_message_as"), QStringLiteral("document-save"), tr("&Save Message..."));
0247     shortcutHandler->defineAction(QStringLiteral("action_view_message_source"), QString(), tr("View Message &Source..."));
0248     shortcutHandler->defineAction(QStringLiteral("action_view_message_headers"), QString(), tr("View Message &Headers..."), tr("Ctrl+U"));
0249     shortcutHandler->defineAction(QStringLiteral("action_reply_private"), QStringLiteral("mail-reply-sender"), tr("&Private Reply"), tr("Ctrl+Shift+A"));
0250     shortcutHandler->defineAction(QStringLiteral("action_reply_all_but_me"), QStringLiteral("mail-reply-all"), tr("Reply to All &but Me"), tr("Ctrl+Shift+R"));
0251     shortcutHandler->defineAction(QStringLiteral("action_reply_all"), QStringLiteral("mail-reply-all"), tr("Reply to &All"), tr("Ctrl+Alt+Shift+R"));
0252     shortcutHandler->defineAction(QStringLiteral("action_reply_list"), QStringLiteral("mail-reply-list"), tr("Reply to &Mailing List"), tr("Ctrl+L"));
0253     shortcutHandler->defineAction(QStringLiteral("action_reply_guess"), QString(), tr("Reply by &Guess"), tr("Ctrl+R"));
0254     shortcutHandler->defineAction(QStringLiteral("action_forward_attachment"), QStringLiteral("mail-forward"), tr("&Forward"), tr("Ctrl+Shift+F"));
0255     shortcutHandler->defineAction(QStringLiteral("action_resend"), QStringLiteral("mail-resend"), tr("Resend..."));
0256     shortcutHandler->defineAction(QStringLiteral("action_archive"), QStringLiteral("mail-move-to-archive"), tr("&Archive"), QStringLiteral("A"));
0257     shortcutHandler->defineAction(QStringLiteral("action_contact_editor"), QStringLiteral("contact-unknown"), tr("Address Book..."));
0258     shortcutHandler->defineAction(QStringLiteral("action_network_offline"), QStringLiteral("network-disconnect"), tr("&Offline"));
0259     shortcutHandler->defineAction(QStringLiteral("action_network_expensive"), QStringLiteral("network-wireless"), tr("&Expensive Connection"));
0260     shortcutHandler->defineAction(QStringLiteral("action_network_online"), QStringLiteral("network-connect"), tr("&Free Access"));
0261     shortcutHandler->defineAction(QStringLiteral("action_messagewindow_close"), QStringLiteral("window-close"), tr("Close Standalone Message Window"));
0262     shortcutHandler->defineAction(QStringLiteral("action_open_messagewindow"), QString(), tr("Open message in New Window..."), QStringLiteral("Ctrl+Return"));
0263     shortcutHandler->defineAction(QStringLiteral("action_oneattime_go_back"), QStringLiteral("go-previous"), tr("Navigate Back"), QKeySequence(QKeySequence::Back).toString());
0264     shortcutHandler->defineAction(QStringLiteral("action_zoom_in"), QStringLiteral("zoom-in"), tr("Zoom In"), QKeySequence::ZoomIn);
0265     shortcutHandler->defineAction(QStringLiteral("action_zoom_out"), QStringLiteral("zoom-out"), tr("Zoom Out"), QKeySequence::ZoomOut);
0266     shortcutHandler->defineAction(QStringLiteral("action_zoom_original"), QStringLiteral("zoom-original"), tr("Original Size"));
0267     shortcutHandler->defineAction(QStringLiteral("action_focus_mailbox_tree"), QString(), tr("Move Focus to Mailbox List"));
0268     shortcutHandler->defineAction(QStringLiteral("action_focus_msg_list"), QString(), tr("Move Focus to Message List"));
0269     shortcutHandler->defineAction(QStringLiteral("action_focus_quick_search"), QString(), tr("Move Focus to Quick Search"), QStringLiteral("/"));
0270     shortcutHandler->defineAction(QStringLiteral("action_tag_1"), QStringLiteral("mail-tag-1"), tr("Tag with &1st tag"), QStringLiteral("1"));
0271     shortcutHandler->defineAction(QStringLiteral("action_tag_2"), QStringLiteral("mail-tag-2"), tr("Tag with &2nd tag"), QStringLiteral("2"));
0272     shortcutHandler->defineAction(QStringLiteral("action_tag_3"), QStringLiteral("mail-tag-3"), tr("Tag with &3rd tag"), QStringLiteral("3"));
0273     shortcutHandler->defineAction(QStringLiteral("action_tag_4"), QStringLiteral("mail-tag-4"), tr("Tag with &4th tag"), QStringLiteral("4"));
0274     shortcutHandler->defineAction(QStringLiteral("action_tag_5"), QStringLiteral("mail-tag-5"), tr("Tag with &5th tag"), QStringLiteral("5"));
0275     shortcutHandler->defineAction(QStringLiteral("action_tag_6"), QStringLiteral("mail-tag-6"), tr("Tag with &6th tag"), QStringLiteral("6"));
0276     shortcutHandler->defineAction(QStringLiteral("action_tag_7"), QStringLiteral("mail-tag-7"), tr("Tag with &7th tag"), QStringLiteral("7"));
0277     shortcutHandler->defineAction(QStringLiteral("action_tag_8"), QStringLiteral("mail-tag-8"), tr("Tag with &8th tag"), QStringLiteral("8"));
0278     shortcutHandler->defineAction(QStringLiteral("action_tag_9"), QStringLiteral("mail-tag-9"), tr("Tag with &9th tag"), QStringLiteral("9"));
0279 }
0280 
0281 void MainWindow::createActions()
0282 {
0283     // The shortcuts are a little bit complicated, unfortunately. This is what the other applications use by default:
0284     //
0285     // Thunderbird:
0286     // private: Ctrl+R
0287     // all: Ctrl+Shift+R
0288     // list: Ctrl+Shift+L
0289     // forward: Ctrl+L
0290     // (no shortcuts for type of forwarding)
0291     // resend: ctrl+B
0292     // new message: Ctrl+N
0293     //
0294     // KMail:
0295     // "reply": R
0296     // private: Shift+A
0297     // all: A
0298     // list: L
0299     // forward as attachment: F
0300     // forward inline: Shift+F
0301     // resend: E
0302     // new: Ctrl+N
0303 
0304     m_actionContactEditor = ShortcutHandler::instance()->createAction(QStringLiteral("action_contact_editor"), this, SLOT(invokeContactEditor()), this);
0305 
0306     m_mainToolbar = addToolBar(tr("Navigation"));
0307     m_mainToolbar->setObjectName(QStringLiteral("mainToolbar"));
0308 
0309     reloadMboxList = new QAction(style()->standardIcon(QStyle::SP_ArrowRight), tr("&Update List of Child Mailboxes"), this);
0310     connect(reloadMboxList, &QAction::triggered, this, &MainWindow::slotReloadMboxList);
0311 
0312     resyncMbox = new QAction(UiUtils::loadIcon(QStringLiteral("view-refresh")), tr("Check for &New Messages"), this);
0313     connect(resyncMbox, &QAction::triggered, this, &MainWindow::slotResyncMbox);
0314 
0315     reloadAllMailboxes = new QAction(tr("&Reload Everything"), this);
0316     // connect later
0317 
0318     exitAction = ShortcutHandler::instance()->createAction(QStringLiteral("action_application_exit"), qApp, SLOT(quit()), this);
0319     exitAction->setStatusTip(tr("Exit the application"));
0320 
0321     netOffline = ShortcutHandler::instance()->createAction(QStringLiteral("action_network_offline"));
0322     netOffline->setCheckable(true);
0323     // connect later
0324     netExpensive = ShortcutHandler::instance()->createAction(QStringLiteral("action_network_expensive"));
0325     netExpensive->setCheckable(true);
0326     // connect later
0327     netOnline = ShortcutHandler::instance()->createAction(QStringLiteral("action_network_online"));
0328     netOnline->setCheckable(true);
0329     // connect later
0330 
0331     QActionGroup *netPolicyGroup = new QActionGroup(this);
0332     netPolicyGroup->setExclusive(true);
0333     netPolicyGroup->addAction(netOffline);
0334     netPolicyGroup->addAction(netExpensive);
0335     netPolicyGroup->addAction(netOnline);
0336 
0337     //: a debugging tool showing the full contents of the whole IMAP server; all folders, messages and their parts
0338     showFullView = new QAction(UiUtils::loadIcon(QStringLiteral("edit-find-mail")), tr("Show Full &Tree Window"), this);
0339     showFullView->setCheckable(true);
0340     connect(showFullView, &QAction::triggered, allDock, &QWidget::setVisible);
0341     connect(allDock, &QDockWidget::visibilityChanged, showFullView, &QAction::setChecked);
0342 
0343     //: list of active "tasks", entities which are performing certain action like downloading a message or syncing a mailbox
0344     showTaskView = new QAction(tr("Show ImapTask t&ree"), this);
0345     showTaskView->setCheckable(true);
0346     connect(showTaskView, &QAction::triggered, taskDock, &QWidget::setVisible);
0347     connect(taskDock, &QDockWidget::visibilityChanged, showTaskView, &QAction::setChecked);
0348 
0349     //: a debugging tool showing the mime tree of the current message
0350     showMimeView = new QAction(tr("Show &MIME tree"), this);
0351     showMimeView->setCheckable(true);
0352     connect(showMimeView, &QAction::triggered, mailMimeDock, &QWidget::setVisible);
0353     connect(mailMimeDock, &QDockWidget::visibilityChanged, showMimeView, &QAction::setChecked);
0354 
0355     showProtocolLogger = new QAction(tr("Show protocol &log"), this);
0356     showProtocolLogger->setCheckable(true);
0357     connect(showProtocolLogger, &QAction::toggled, protocolLoggerDock, &QWidget::setVisible);
0358     connect(protocolLoggerDock, &QDockWidget::visibilityChanged, showProtocolLogger, &QAction::setChecked);
0359 
0360     //: file to save the debug log into
0361     logPersistent = new QAction(tr("Log &into %1").arg(Imap::Mailbox::persistentLogFileName()), this);
0362     logPersistent->setCheckable(true);
0363     connect(logPersistent, &QAction::triggered, protocolLogger, &ProtocolLoggerWidget::slotSetPersistentLogging);
0364     connect(protocolLogger, &ProtocolLoggerWidget::persistentLoggingChanged, logPersistent, &QAction::setChecked);
0365 
0366     showImapCapabilities = new QAction(tr("IMAP Server In&formation..."), this);
0367     connect(showImapCapabilities, &QAction::triggered, this, &MainWindow::slotShowImapInfo);
0368 
0369     showMenuBar = ShortcutHandler::instance()->createAction(QStringLiteral("action_show_menubar"), this);
0370     showMenuBar->setCheckable(true);
0371     showMenuBar->setChecked(true);
0372     connect(showMenuBar, &QAction::triggered, menuBar(), &QMenuBar::setVisible);
0373     connect(showMenuBar, &QAction::triggered, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
0374 
0375     showToolBar = new QAction(tr("Show &Toolbar"), this);
0376     showToolBar->setCheckable(true);
0377     connect(showToolBar, &QAction::triggered, m_mainToolbar, &QWidget::setVisible);
0378     connect(m_mainToolbar, &QToolBar::visibilityChanged, showToolBar, &QAction::setChecked);
0379     connect(m_mainToolbar, &QToolBar::visibilityChanged, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
0380 
0381     configSettings = new QAction(UiUtils::loadIcon(QStringLiteral("configure")),  tr("&Settings..."), this);
0382     connect(configSettings, &QAction::triggered, this, &MainWindow::slotShowSettings);
0383 
0384     QAction *triggerSearch = new QAction(this);
0385     addAction(triggerSearch);
0386     triggerSearch->setShortcut(QKeySequence(QStringLiteral(":, =")));
0387     connect(triggerSearch, &QAction::triggered, msgListWidget, &MessageListWidget::focusRawSearch);
0388 
0389     addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_focus_quick_search"),
0390             msgListWidget, SLOT(focusSearch()), this));
0391 
0392     addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_focus_mailbox_tree"), mboxTree,
0393             SLOT(setFocus()), this));
0394     addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_focus_msg_list"), msgListWidget->tree,
0395             SLOT(setFocus()), this));
0396 
0397     m_oneAtTimeGoBack = ShortcutHandler::instance()->createAction(QStringLiteral("action_oneattime_go_back"), this);
0398     m_oneAtTimeGoBack->setEnabled(false);
0399 
0400     composeMail = ShortcutHandler::instance()->createAction(QStringLiteral("action_compose_mail"), this, SLOT(slotComposeMail()), this);
0401     m_editDraft = ShortcutHandler::instance()->createAction(QStringLiteral("action_compose_draft"), this, SLOT(slotEditDraft()), this);
0402 
0403     expunge = ShortcutHandler::instance()->createAction(QStringLiteral("action_expunge"), this, SLOT(slotExpunge()), this);
0404 
0405     m_forwardAsAttachment = ShortcutHandler::instance()->createAction(QStringLiteral("action_forward_attachment"), this, SLOT(slotForwardAsAttachment()), this);
0406     m_resend = ShortcutHandler::instance()->createAction(QStringLiteral("action_resend"), this, SLOT(slotResend()), this);
0407     markAsRead = ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_read"), this);
0408     markAsRead->setCheckable(true);
0409     msgListWidget->tree->addAction(markAsRead);
0410     connect(markAsRead, &QAction::triggered, this, &MainWindow::handleMarkAsRead);
0411 
0412     m_nextMessage = ShortcutHandler::instance()->createAction(QStringLiteral("action_go_to_next_unread"), this, SLOT(slotNextUnread()), this);
0413     msgListWidget->tree->addAction(m_nextMessage);
0414     m_messageWidget->messageView->addAction(m_nextMessage);
0415 
0416     m_previousMessage = ShortcutHandler::instance()->createAction(QStringLiteral("action_go_to_previous_unread"), this, SLOT(slotPreviousUnread()), this);
0417     msgListWidget->tree->addAction(m_previousMessage);
0418     m_messageWidget->messageView->addAction(m_previousMessage);
0419 
0420     markAsDeleted = ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_deleted"), this);
0421     markAsDeleted->setCheckable(true);
0422     msgListWidget->tree->addAction(markAsDeleted);
0423     connect(markAsDeleted, &QAction::triggered, this, &MainWindow::handleMarkAsDeleted);
0424 
0425     markAsFlagged = ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_flagged"), this);
0426     markAsFlagged->setCheckable(true);
0427     msgListWidget->tree->addAction(markAsFlagged);
0428     connect(markAsFlagged, &QAction::triggered, this, &MainWindow::handleMarkAsFlagged);
0429 
0430     markAsJunk = ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_junk"), this);
0431     markAsJunk->setCheckable(true);
0432     msgListWidget->tree->addAction(markAsJunk);
0433     connect(markAsJunk, &QAction::triggered, this, &MainWindow::handleMarkAsJunk);
0434 
0435     markAsNotJunk = ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_notjunk"), this);
0436     markAsNotJunk->setCheckable(true);
0437     msgListWidget->tree->addAction(markAsNotJunk);
0438     connect(markAsNotJunk, &QAction::triggered, this, &MainWindow::handleMarkAsNotJunk);
0439 
0440     saveWholeMessage = ShortcutHandler::instance()->createAction(QStringLiteral("action_save_message_as"), this, SLOT(slotSaveCurrentMessageBody()), this);
0441     msgListWidget->tree->addAction(saveWholeMessage);
0442 
0443     viewMsgSource = ShortcutHandler::instance()->createAction(QStringLiteral("action_view_message_source"), this, SLOT(slotViewMsgSource()), this);
0444     msgListWidget->tree->addAction(viewMsgSource);
0445 
0446     viewMsgHeaders = ShortcutHandler::instance()->createAction(QStringLiteral("action_view_message_headers"), this, SLOT(slotViewMsgHeaders()), this);
0447     msgListWidget->tree->addAction(viewMsgHeaders);
0448 
0449     msgListWidget->tree->addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_open_messagewindow"), this,
0450             SLOT(openCompleteMessageWidget()), this));
0451 
0452     moveToArchive = ShortcutHandler::instance()->createAction(QStringLiteral("action_archive"), this);
0453     msgListWidget->tree->addAction(moveToArchive);
0454     connect(moveToArchive, &QAction::triggered, this, &MainWindow::handleMoveToArchive);
0455 
0456     auto addTagAction = [=](int row) {
0457         QAction *tag = ShortcutHandler::instance()->createAction(QStringLiteral("action_tag_").append(QString::number(row)), this);
0458         tag->setCheckable(true);
0459         msgListWidget->tree->addAction(tag);
0460         connect(tag, &QAction::triggered, this, [=](const bool checked) {
0461             handleTag(checked, row - 1);
0462         });
0463         return tag;
0464     };
0465     tag1 = addTagAction(1);
0466     tag2 = addTagAction(2);
0467     tag3 = addTagAction(3);
0468     tag4 = addTagAction(4);
0469     tag5 = addTagAction(5);
0470     tag6 = addTagAction(6);
0471     tag7 = addTagAction(7);
0472     tag8 = addTagAction(8);
0473     tag9 = addTagAction(9);
0474 
0475     //: "mailbox" as a "folder of messages", not as a "mail account"
0476     createChildMailbox = new QAction(tr("Create &Child Mailbox..."), this);
0477     connect(createChildMailbox, &QAction::triggered, this, &MainWindow::slotCreateMailboxBelowCurrent);
0478 
0479     //: "mailbox" as a "folder of messages", not as a "mail account"
0480     createTopMailbox = new QAction(tr("Create &New Mailbox..."), this);
0481     connect(createTopMailbox, &QAction::triggered, this, &MainWindow::slotCreateTopMailbox);
0482 
0483     m_actionMarkMailboxAsRead = new QAction(tr("&Mark Mailbox as Read"), this);
0484     connect(m_actionMarkMailboxAsRead, &QAction::triggered, this, &MainWindow::slotMarkCurrentMailboxRead);
0485 
0486     //: "mailbox" as a "folder of messages", not as a "mail account"
0487     deleteCurrentMailbox = new QAction(tr("&Remove Mailbox"), this);
0488     connect(deleteCurrentMailbox, &QAction::triggered, this, &MainWindow::slotDeleteCurrentMailbox);
0489 
0490     m_replyPrivate = ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_private"), this, SLOT(slotReplyTo()), this);
0491     m_replyPrivate->setEnabled(false);
0492 
0493     m_replyAllButMe = ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_all_but_me"), this, SLOT(slotReplyAllButMe()), this);
0494     m_replyAllButMe->setEnabled(false);
0495 
0496     m_replyAll = ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_all"), this, SLOT(slotReplyAll()), this);
0497     m_replyAll->setEnabled(false);
0498 
0499     m_replyList = ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_list"), this, SLOT(slotReplyList()), this);
0500     m_replyList->setEnabled(false);
0501 
0502     m_replyGuess = ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_guess"), this, SLOT(slotReplyGuess()), this);
0503     m_replyGuess->setEnabled(true);
0504 
0505     actionThreadMsgList = new QAction(UiUtils::loadIcon(QStringLiteral("format-justify-right")), tr("Show Messages in &Threads"), this);
0506     actionThreadMsgList->setCheckable(true);
0507     // This action is enabled/disabled by model's capabilities
0508     actionThreadMsgList->setEnabled(false);
0509     if (m_settings->value(Common::SettingsNames::guiMsgListShowThreading).toBool()) {
0510         actionThreadMsgList->setChecked(true);
0511         // The actual threading will be performed only when model updates its capabilities
0512     }
0513     connect(actionThreadMsgList, &QAction::triggered, this, &MainWindow::slotThreadMsgList);
0514 
0515     QActionGroup *sortOrderGroup = new QActionGroup(this);
0516     m_actionSortAscending = new QAction(tr("&Ascending"), sortOrderGroup);
0517     m_actionSortAscending->setCheckable(true);
0518     m_actionSortAscending->setChecked(true);
0519     m_actionSortDescending = new QAction(tr("&Descending"), sortOrderGroup);
0520     m_actionSortDescending->setCheckable(true);
0521     // QActionGroup has no toggle signal, but connecting descending will implicitly catch the acscending complement ;-)
0522     connect(m_actionSortDescending, &QAction::toggled, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
0523     connect(m_actionSortDescending, &QAction::toggled, this, &MainWindow::slotScrollToCurrent);
0524     connect(sortOrderGroup, &QActionGroup::triggered, this, &MainWindow::slotSortingPreferenceChanged);
0525 
0526     QActionGroup *sortColumnGroup = new QActionGroup(this);
0527     m_actionSortNone = new QAction(tr("&No sorting"), sortColumnGroup);
0528     m_actionSortNone->setCheckable(true);
0529     m_actionSortThreading = new QAction(tr("Sorted by &Threading"), sortColumnGroup);
0530     m_actionSortThreading->setCheckable(true);
0531     m_actionSortByArrival = new QAction(tr("A&rrival"), sortColumnGroup);
0532     m_actionSortByArrival->setCheckable(true);
0533     m_actionSortByCc = new QAction(tr("&Cc (Carbon Copy)"), sortColumnGroup);
0534     m_actionSortByCc->setCheckable(true);
0535     m_actionSortByDate = new QAction(tr("Date from &Message Headers"), sortColumnGroup);
0536     m_actionSortByDate->setCheckable(true);
0537     m_actionSortByFrom = new QAction(tr("&From Address"), sortColumnGroup);
0538     m_actionSortByFrom->setCheckable(true);
0539     m_actionSortBySize = new QAction(tr("&Size"), sortColumnGroup);
0540     m_actionSortBySize->setCheckable(true);
0541     m_actionSortBySubject = new QAction(tr("S&ubject"), sortColumnGroup);
0542     m_actionSortBySubject->setCheckable(true);
0543     m_actionSortByTo = new QAction(tr("T&o Address"), sortColumnGroup);
0544     m_actionSortByTo->setCheckable(true);
0545     connect(sortColumnGroup, &QActionGroup::triggered, this, &MainWindow::slotSortingPreferenceChanged);
0546     slotSortingConfirmed(-1, Qt::AscendingOrder);
0547 
0548     actionHideRead = new QAction(tr("&Hide Read Messages"), this);
0549     actionHideRead->setCheckable(true);
0550     if (m_settings->value(Common::SettingsNames::guiMsgListHideRead).toBool()) {
0551         actionHideRead->setChecked(true);
0552         prettyMsgListModel->setHideRead(true);
0553     }
0554     connect(actionHideRead, &QAction::triggered, this, &MainWindow::slotHideRead);
0555 
0556     QActionGroup *layoutGroup = new QActionGroup(this);
0557     m_actionLayoutCompact = new QAction(tr("&Compact"), layoutGroup);
0558     m_actionLayoutCompact->setCheckable(true);
0559     m_actionLayoutCompact->setChecked(true);
0560     connect(m_actionLayoutCompact, &QAction::triggered, this, &MainWindow::slotLayoutCompact);
0561     m_actionLayoutWide = new QAction(tr("&Wide"), layoutGroup);
0562     m_actionLayoutWide->setCheckable(true);
0563     connect(m_actionLayoutWide, &QAction::triggered, this, &MainWindow::slotLayoutWide);
0564     m_actionLayoutOneAtTime = new QAction(tr("&One At a Time"), layoutGroup);
0565     m_actionLayoutOneAtTime->setCheckable(true);
0566     connect(m_actionLayoutOneAtTime, &QAction::triggered, this, &MainWindow::slotLayoutOneAtTime);
0567 
0568 
0569     m_actionShowOnlySubscribed = new QAction(tr("Show Only S&ubscribed Folders"), this);
0570     m_actionShowOnlySubscribed->setCheckable(true);
0571     m_actionShowOnlySubscribed->setEnabled(false);
0572     connect(m_actionShowOnlySubscribed, &QAction::toggled, this, &MainWindow::slotShowOnlySubscribed);
0573     m_actionSubscribeMailbox = new QAction(tr("Su&bscribed"), this);
0574     m_actionSubscribeMailbox->setCheckable(true);
0575     m_actionSubscribeMailbox->setEnabled(false);
0576     connect(m_actionSubscribeMailbox, &QAction::triggered, this, &MainWindow::slotSubscribeCurrentMailbox);
0577 
0578     aboutTrojita = new QAction(tr("&About Trojitá..."), this);
0579     connect(aboutTrojita, &QAction::triggered, this, &MainWindow::slotShowAboutTrojita);
0580 
0581     donateToTrojita = new QAction(tr("&Donate to the project"), this);
0582     connect(donateToTrojita, &QAction::triggered, this, &MainWindow::slotDonateToTrojita);
0583 
0584     connectModelActions();
0585 
0586     m_composeMenu = new QMenu(tr("Compose Mail"), this);
0587     m_composeMenu->addAction(composeMail);
0588     m_composeMenu->addAction(m_editDraft);
0589     m_composeButton = new QToolButton(this);
0590     m_composeButton->setPopupMode(QToolButton::MenuButtonPopup);
0591     m_composeButton->setMenu(m_composeMenu);
0592     m_composeButton->setDefaultAction(composeMail);
0593 
0594     m_replyButton = new QToolButton(this);
0595     m_replyButton->setPopupMode(QToolButton::MenuButtonPopup);
0596     m_replyMenu = new QMenu(m_replyButton);
0597     m_replyMenu->addAction(m_replyPrivate);
0598     m_replyMenu->addAction(m_replyAllButMe);
0599     m_replyMenu->addAction(m_replyAll);
0600     m_replyMenu->addAction(m_replyList);
0601     m_replyButton->setMenu(m_replyMenu);
0602     m_replyButton->setDefaultAction(m_replyPrivate);
0603 
0604     m_mainToolbar->addWidget(m_composeButton);
0605     m_mainToolbar->addWidget(m_replyButton);
0606     m_mainToolbar->addAction(m_forwardAsAttachment);
0607     m_mainToolbar->addAction(expunge);
0608     m_mainToolbar->addSeparator();
0609     m_mainToolbar->addAction(markAsRead);
0610     m_mainToolbar->addAction(markAsDeleted);
0611     m_mainToolbar->addAction(markAsFlagged);
0612     m_mainToolbar->addAction(markAsJunk);
0613     m_mainToolbar->addAction(markAsNotJunk);
0614     m_mainToolbar->addAction(moveToArchive);
0615 
0616     // Push the status indicators all the way to the other side of the toolbar -- either to the far right, or far bottom.
0617     QWidget *toolbarSpacer = new QWidget(m_mainToolbar);
0618     toolbarSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0619     m_mainToolbar->addWidget(toolbarSpacer);
0620 
0621     m_mainToolbar->addSeparator();
0622     m_mainToolbar->addWidget(busyParsersIndicator);
0623 
0624     networkIndicator = new QToolButton(this);
0625     // This is deliberate; we want to show this button in the same style as the other ones in the toolbar
0626     networkIndicator->setPopupMode(QToolButton::MenuButtonPopup);
0627     m_mainToolbar->addWidget(networkIndicator);
0628 
0629     m_menuFromToolBar = new QToolButton(this);
0630     m_menuFromToolBar->setIcon(UiUtils::loadIcon(QStringLiteral("menu_new")));
0631     m_menuFromToolBar->setText(QChar(0x205d)); // Unicode 'TRICOLON'
0632     m_menuFromToolBar->setPopupMode(QToolButton::MenuButtonPopup);
0633     connect(m_menuFromToolBar, &QAbstractButton::clicked, m_menuFromToolBar, &QToolButton::showMenu);
0634     m_mainToolbar->addWidget(m_menuFromToolBar);
0635     connect(showMenuBar, &QAction::toggled, [this](const bool menuBarVisible) {
0636         // https://bugreports.qt.io/browse/QTBUG-35768 , we have to work on the QAction, not QToolButton
0637         m_mainToolbar->actions().last()->setVisible(!menuBarVisible);
0638     });
0639 
0640     busyParsersIndicator->setFixedSize(m_mainToolbar->iconSize());
0641 
0642     {
0643         // Custom widgets which are added into a QToolBar are by default aligned to the left, while QActions are justified.
0644         // That sucks, because some of our widgets use multiple actions with an expanding arrow at right.
0645         // Make sure everything is aligned to the left, so that the actual buttons are aligned properly and the extra arrows
0646         // are, well, at right.
0647         // I have no idea how this works on RTL layouts.
0648         QLayout *lay = m_mainToolbar->layout();
0649         for (int i = 0; i < lay->count(); ++i) {
0650             QLayoutItem *it = lay->itemAt(i);
0651             if (it->widget() == toolbarSpacer) {
0652                 // Don't align this one, otherwise it won't push stuff when in horizontal direction
0653                 continue;
0654             }
0655             if (it->widget() == busyParsersIndicator) {
0656                 // It looks much better when centered
0657                 it->setAlignment(Qt::AlignJustify);
0658                 continue;
0659             }
0660             it->setAlignment(Qt::AlignLeft);
0661         }
0662     }
0663 
0664     updateMessageFlags();
0665 }
0666 
0667 void MainWindow::connectModelActions()
0668 {
0669     connect(reloadAllMailboxes, &QAction::triggered, imapModel(), &Imap::Mailbox::Model::reloadMailboxList);
0670     connect(netOffline, &QAction::triggered,
0671             qobject_cast<Imap::Mailbox::NetworkWatcher*>(m_imapAccess->networkWatcher()), &Imap::Mailbox::NetworkWatcher::setNetworkOffline);
0672     connect(netExpensive, &QAction::triggered,
0673             qobject_cast<Imap::Mailbox::NetworkWatcher*>(m_imapAccess->networkWatcher()), &Imap::Mailbox::NetworkWatcher::setNetworkExpensive);
0674     connect(netOnline, &QAction::triggered,
0675             qobject_cast<Imap::Mailbox::NetworkWatcher*>(m_imapAccess->networkWatcher()), &Imap::Mailbox::NetworkWatcher::setNetworkOnline);
0676     netExpensive->setEnabled(imapAccess()->isConfigured());
0677     netOnline->setEnabled(imapAccess()->isConfigured());
0678 }
0679 
0680 void MainWindow::createMenus()
0681 {
0682 #define ADD_ACTION(MENU, ACTION) \
0683     MENU->addAction(ACTION); \
0684     addAction(ACTION);
0685 
0686     QMenu *applicationMenu = menuBar()->addMenu(tr("&Application"));
0687         ADD_ACTION(applicationMenu, m_actionContactEditor);
0688         QMenu *netPolicyMenu = applicationMenu->addMenu(tr("&Network Access"));
0689             ADD_ACTION(netPolicyMenu, netOffline);
0690             ADD_ACTION(netPolicyMenu, netExpensive);
0691             ADD_ACTION(netPolicyMenu, netOnline);
0692         QMenu *debugMenu = applicationMenu->addMenu(tr("&Debugging"));
0693             ADD_ACTION(debugMenu, showFullView);
0694             ADD_ACTION(debugMenu, showTaskView);
0695             ADD_ACTION(debugMenu, showMimeView);
0696             ADD_ACTION(debugMenu, showProtocolLogger);
0697             ADD_ACTION(debugMenu, logPersistent);
0698             debugMenu->addSeparator();
0699             ADD_ACTION(debugMenu, showImapCapabilities);
0700             debugMenu->addSeparator();
0701             ADD_ACTION(debugMenu, reloadAllMailboxes);
0702             ADD_ACTION(debugMenu, resyncMbox);
0703         applicationMenu->addSeparator();
0704         ADD_ACTION(applicationMenu, configSettings);
0705         ADD_ACTION(applicationMenu, ShortcutHandler::instance()->shortcutConfigAction());
0706         applicationMenu->addSeparator();
0707         ADD_ACTION(applicationMenu, aboutTrojita);
0708         ADD_ACTION(applicationMenu, donateToTrojita);
0709         applicationMenu->addSeparator();
0710         ADD_ACTION(applicationMenu, exitAction);
0711 
0712     QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
0713         ADD_ACTION(viewMenu, showMenuBar);
0714         ADD_ACTION(viewMenu, showToolBar);
0715         QMenu *layoutMenu = viewMenu->addMenu(tr("&Layout"));
0716             ADD_ACTION(layoutMenu, m_actionLayoutCompact);
0717             ADD_ACTION(layoutMenu, m_actionLayoutWide);
0718             ADD_ACTION(layoutMenu, m_actionLayoutOneAtTime);
0719         viewMenu->addSeparator();
0720         ADD_ACTION(viewMenu, m_actionShowOnlySubscribed);
0721 
0722     QMenu *mailboxMenu = menuBar()->addMenu(tr("Mail&box"));
0723         QMenu *sortMenu = mailboxMenu->addMenu(tr("S&orting"));
0724             ADD_ACTION(sortMenu, m_actionSortNone);
0725             ADD_ACTION(sortMenu, m_actionSortThreading);
0726             ADD_ACTION(sortMenu, m_actionSortByArrival);
0727             ADD_ACTION(sortMenu, m_actionSortByCc);
0728             ADD_ACTION(sortMenu, m_actionSortByDate);
0729             ADD_ACTION(sortMenu, m_actionSortByFrom);
0730             ADD_ACTION(sortMenu, m_actionSortBySize);
0731             ADD_ACTION(sortMenu, m_actionSortBySubject);
0732             ADD_ACTION(sortMenu, m_actionSortByTo);
0733             sortMenu->addSeparator();
0734             ADD_ACTION(sortMenu, m_actionSortAscending);
0735             ADD_ACTION(sortMenu, m_actionSortDescending);
0736             sortMenu->addSeparator();
0737             ADD_ACTION(sortMenu, actionThreadMsgList);
0738             ADD_ACTION(sortMenu, actionHideRead);
0739         mailboxMenu->addSeparator();
0740         ADD_ACTION(mailboxMenu, m_previousMessage);
0741         ADD_ACTION(mailboxMenu, m_nextMessage);
0742         mailboxMenu->addSeparator();
0743         ADD_ACTION(mailboxMenu, expunge);
0744 
0745     QMenu *messageMenu = menuBar()->addMenu(tr("&Message"));
0746         ADD_ACTION(messageMenu, composeMail);
0747         ADD_ACTION(messageMenu, m_editDraft);
0748         messageMenu->addSeparator();
0749         ADD_ACTION(messageMenu, m_replyGuess);
0750         ADD_ACTION(messageMenu, m_replyPrivate);
0751         ADD_ACTION(messageMenu, m_replyAll);
0752         ADD_ACTION(messageMenu, m_replyAllButMe);
0753         ADD_ACTION(messageMenu, m_replyList);
0754         messageMenu->addSeparator();
0755         ADD_ACTION(messageMenu, m_forwardAsAttachment);
0756         ADD_ACTION(messageMenu, m_resend);
0757 
0758     QMenu *mainMenuBehindToolBar = new QMenu(this);
0759     m_menuFromToolBar->setMenu(mainMenuBehindToolBar);
0760     m_menuFromToolBar->menu()->addMenu(applicationMenu);
0761     m_menuFromToolBar->menu()->addMenu(viewMenu);
0762     m_menuFromToolBar->menu()->addMenu(mailboxMenu);
0763     m_menuFromToolBar->menu()->addMenu(messageMenu);
0764     m_menuFromToolBar->menu()->addSeparator();
0765     m_menuFromToolBar->menu()->addAction(showMenuBar);
0766 
0767     networkIndicator->setMenu(netPolicyMenu);
0768     m_netToolbarDefaultAction = new QAction(this);
0769     networkIndicator->setDefaultAction(m_netToolbarDefaultAction);
0770     connect(m_netToolbarDefaultAction, &QAction::triggered, networkIndicator, &QToolButton::showMenu);
0771     connect(netOffline, &QAction::toggled, this, &MainWindow::updateNetworkIndication);
0772     connect(netExpensive, &QAction::toggled, this, &MainWindow::updateNetworkIndication);
0773     connect(netOnline, &QAction::toggled, this, &MainWindow::updateNetworkIndication);
0774 
0775     addToolBar(Qt::LeftToolBarArea, m_mainToolbar);
0776     m_mainToolbar->actions().last()->setVisible(true); // initial state to complement the default of the QMenuBar's visibility
0777     menuBar()->hide();
0778 
0779 #undef ADD_ACTION
0780 }
0781 
0782 void MainWindow::createWidgets()
0783 {
0784     // The state of the GUI is only saved after a certain time has passed. This is just an optimization to make sure
0785     // we do not hit the disk continually when e.g. resizing some random widget.
0786     m_delayedStateSaving = new QTimer(this);
0787     m_delayedStateSaving->setInterval(1000);
0788     m_delayedStateSaving->setSingleShot(true);
0789     connect(m_delayedStateSaving, &QTimer::timeout, this, &MainWindow::saveSizesAndState);
0790 
0791     mboxTree = new MailBoxTreeView(nullptr, m_settings);
0792     mboxTree->setDesiredExpansion(m_settings->value(Common::SettingsNames::guiExpandedMailboxes).toStringList());
0793     connect(mboxTree, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenuMboxTree);
0794     connect(mboxTree, &MailBoxTreeView::mailboxExpansionChanged, this, [this](const QStringList &mailboxNames) {
0795         m_settings->setValue(Common::SettingsNames::guiExpandedMailboxes, mailboxNames);
0796     });
0797 
0798     msgListWidget = new MessageListWidget(nullptr, m_favoriteTags);
0799     msgListWidget->tree->setContextMenuPolicy(Qt::CustomContextMenu);
0800     msgListWidget->tree->setAlternatingRowColors(true);
0801     msgListWidget->setRawSearchEnabled(m_settings->value(Common::SettingsNames::guiAllowRawSearch).toBool());
0802     connect (msgListWidget, &MessageListWidget::rawSearchSettingChanged, this, &MainWindow::saveRawStateSetting);
0803 
0804     connect(msgListWidget->tree, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenuMsgListTree);
0805     connect(msgListWidget->tree, &QAbstractItemView::activated, this, &MainWindow::msgListClicked);
0806     connect(msgListWidget->tree, &QAbstractItemView::clicked, this, &MainWindow::msgListClicked);
0807     connect(msgListWidget->tree, &QAbstractItemView::doubleClicked, this, &MainWindow::openCompleteMessageWidget);
0808     connect(msgListWidget, &MessageListWidget::requestingSearch, this, &MainWindow::slotSearchRequested);
0809     connect(msgListWidget->tree->header(), &QHeaderView::sectionMoved, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
0810     connect(msgListWidget->tree->header(), &QHeaderView::sectionResized, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
0811 
0812     msgListWidget->tree->installEventFilter(this);
0813 
0814     m_messageWidget = new CompleteMessageWidget(this, m_settings, m_pluginManager, m_favoriteTags);
0815     connect(m_messageWidget->messageView, &MessageView::messageChanged, this, &MainWindow::scrollMessageUp);
0816     connect(m_messageWidget->messageView, &MessageView::messageChanged, this, &MainWindow::slotUpdateMessageActions);
0817 #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
0818     connect(m_messageWidget->messageView, &MessageView::linkHovered, [](const QString &url) {
0819         if (url.isEmpty()) {
0820             QToolTip::hideText();
0821         } else {
0822             // indirection due to https://bugs.kde.org/show_bug.cgi?id=363783
0823             QTimer::singleShot(250, [url]() {
0824                 QToolTip::showText(QCursor::pos(), QObject::tr("Link target: %1").arg(UiUtils::Formatting::htmlEscaped(url)));
0825             });
0826         }
0827     });
0828 #endif
0829     connect(m_messageWidget->messageView, &MessageView::transferError, this, &MainWindow::slotDownloadTransferError);
0830     // Do not try to get onto the homepage when we are on EXPENSIVE connection
0831     if (m_settings->value(Common::SettingsNames::appLoadHomepage, QVariant(true)).toBool() &&
0832         m_imapAccess->preferredNetworkPolicy() == Imap::Mailbox::NETWORK_ONLINE) {
0833         m_messageWidget->messageView->setHomepageUrl(QUrl(QStringLiteral("http://welcome.trojita.flaska.net/%1").arg(Common::Application::version)));
0834     }
0835 
0836     allDock = new QDockWidget(tr("Everything"), this);
0837     allDock->setObjectName(QStringLiteral("allDock"));
0838     allTree = new QTreeView(allDock);
0839     allDock->hide();
0840     allTree->setUniformRowHeights(true);
0841     allTree->setHeaderHidden(true);
0842     allDock->setWidget(allTree);
0843     addDockWidget(Qt::LeftDockWidgetArea, allDock);
0844     taskDock = new QDockWidget(tr("IMAP Tasks"), this);
0845     taskDock->setObjectName(QStringLiteral("taskDock"));
0846     taskTree = new QTreeView(taskDock);
0847     taskDock->hide();
0848     taskTree->setHeaderHidden(true);
0849     taskDock->setWidget(taskTree);
0850     addDockWidget(Qt::LeftDockWidgetArea, taskDock);
0851     mailMimeDock = new QDockWidget(tr("MIME Tree"), this);
0852     mailMimeDock->setObjectName(QStringLiteral("mailMimeDock"));
0853     mailMimeTree = new QTreeView(mailMimeDock);
0854     mailMimeDock->hide();
0855     mailMimeTree->setUniformRowHeights(true);
0856     mailMimeTree->setHeaderHidden(true);
0857     mailMimeDock->setWidget(mailMimeTree);
0858     addDockWidget(Qt::RightDockWidgetArea, mailMimeDock);
0859     connect(m_messageWidget->messageView, &MessageView::messageModelChanged, this, &MainWindow::slotMessageModelChanged);
0860 
0861     protocolLoggerDock = new QDockWidget(tr("Protocol log"), this);
0862     protocolLoggerDock->setObjectName(QStringLiteral("protocolLoggerDock"));
0863     protocolLogger = new ProtocolLoggerWidget(protocolLoggerDock);
0864     protocolLoggerDock->hide();
0865     protocolLoggerDock->setWidget(protocolLogger);
0866     addDockWidget(Qt::BottomDockWidgetArea, protocolLoggerDock);
0867 
0868     busyParsersIndicator = new TaskProgressIndicator(this);
0869 }
0870 
0871 void MainWindow::setupModels()
0872 {
0873     m_imapAccess->reloadConfiguration();
0874     m_imapAccess->doConnect();
0875 
0876     m_messageWidget->messageView->setNetworkWatcher(qobject_cast<Imap::Mailbox::NetworkWatcher*>(m_imapAccess->networkWatcher()));
0877 
0878     auto realThreadingModel = qobject_cast<Imap::Mailbox::ThreadingMsgListModel*>(m_imapAccess->threadingMsgListModel());
0879     Q_ASSERT(realThreadingModel);
0880     auto realMsgListModel = qobject_cast<Imap::Mailbox::MsgListModel*>(m_imapAccess->msgListModel());
0881     Q_ASSERT(realMsgListModel);
0882 
0883     prettyMboxModel = new Imap::Mailbox::PrettyMailboxModel(this, qobject_cast<QAbstractItemModel *>(m_imapAccess->mailboxModel()));
0884     prettyMboxModel->setObjectName(QStringLiteral("prettyMboxModel"));
0885     connect(realThreadingModel, &Imap::Mailbox::ThreadingMsgListModel::sortingFailed,
0886             msgListWidget, &MessageListWidget::slotSortingFailed);
0887     prettyMsgListModel = new Imap::Mailbox::PrettyMsgListModel(this);
0888     prettyMsgListModel->setSourceModel(m_imapAccess->threadingMsgListModel());
0889     prettyMsgListModel->setObjectName(QStringLiteral("prettyMsgListModel"));
0890 
0891     connect(mboxTree, &MailBoxTreeView::clicked,
0892             realMsgListModel,
0893             static_cast<void (Imap::Mailbox::MsgListModel::*)(const QModelIndex &)>(&Imap::Mailbox::MsgListModel::setMailbox));
0894     connect(mboxTree, &MailBoxTreeView::activated,
0895             realMsgListModel,
0896             static_cast<void (Imap::Mailbox::MsgListModel::*)(const QModelIndex &)>(&Imap::Mailbox::MsgListModel::setMailbox));
0897     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::dataChanged, this, &MainWindow::updateMessageFlags);
0898     connect(qobject_cast<Imap::Mailbox::MsgListModel*>(m_imapAccess->msgListModel()), &Imap::Mailbox::MsgListModel::messagesAvailable,
0899             this, &MainWindow::slotScrollToUnseenMessage);
0900     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::rowsInserted, msgListWidget, &MessageListWidget::slotAutoEnableDisableSearch);
0901     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::rowsRemoved, msgListWidget, &MessageListWidget::slotAutoEnableDisableSearch);
0902     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateMessageFlags);
0903     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::layoutChanged, msgListWidget, &MessageListWidget::slotAutoEnableDisableSearch);
0904     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::layoutChanged, this, &MainWindow::updateMessageFlags);
0905     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::modelReset, msgListWidget, &MessageListWidget::slotAutoEnableDisableSearch);
0906     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::modelReset, this, &MainWindow::updateMessageFlags);
0907     connect(realMsgListModel, &Imap::Mailbox::MsgListModel::mailboxChanged, this, &MainWindow::slotMailboxChanged);
0908 
0909     connect(imapModel(), &Imap::Mailbox::Model::alertReceived, this, &MainWindow::alertReceived);
0910     connect(imapModel(), &Imap::Mailbox::Model::imapError, this, &MainWindow::imapError);
0911     connect(imapModel(), &Imap::Mailbox::Model::networkError, this, &MainWindow::networkError);
0912     connect(imapModel(), &Imap::Mailbox::Model::authRequested, this, &MainWindow::authenticationRequested, Qt::QueuedConnection);
0913     connect(imapModel(), &Imap::Mailbox::Model::authAttemptFailed, this, [this]() {
0914         m_ignoreStoredPassword = true;
0915     });
0916 
0917     connect(imapModel(), &Imap::Mailbox::Model::networkPolicyOffline, this, &MainWindow::networkPolicyOffline);
0918     connect(imapModel(), &Imap::Mailbox::Model::networkPolicyExpensive, this, &MainWindow::networkPolicyExpensive);
0919     connect(imapModel(), &Imap::Mailbox::Model::networkPolicyOnline, this, &MainWindow::networkPolicyOnline);
0920     connect(imapModel(), &Imap::Mailbox::Model::connectionStateChanged, this, [this](uint, const Imap::ConnectionState state) {
0921         if (state == Imap::CONN_STATE_AUTHENTICATED) {
0922             m_ignoreStoredPassword = false;
0923         }
0924     });
0925     connect(imapModel(), &Imap::Mailbox::Model::connectionStateChanged, this, &MainWindow::showConnectionStatus);
0926 
0927     connect(imapModel(), &Imap::Mailbox::Model::mailboxDeletionFailed, this, &MainWindow::slotMailboxDeleteFailed);
0928     connect(imapModel(), &Imap::Mailbox::Model::mailboxCreationFailed, this, &MainWindow::slotMailboxCreateFailed);
0929     connect(imapModel(), &Imap::Mailbox::Model::mailboxSyncFailed, this, &MainWindow::slotMailboxSyncFailed);
0930 
0931     connect(imapModel(), &Imap::Mailbox::Model::logged, protocolLogger, &ProtocolLoggerWidget::log);
0932     connect(imapModel(), &Imap::Mailbox::Model::connectionStateChanged, protocolLogger, &ProtocolLoggerWidget::onConnectionClosed);
0933 
0934     auto nw = qobject_cast<Imap::Mailbox::NetworkWatcher *>(m_imapAccess->networkWatcher());
0935     Q_ASSERT(nw);
0936     connect(nw, &Imap::Mailbox::NetworkWatcher::reconnectAttemptScheduled,
0937             this, [this](const int timeout) {
0938             showStatusMessage(tr("Attempting to reconnect in %n seconds..", 0, timeout/1000));
0939             });
0940     connect(nw, &Imap::Mailbox::NetworkWatcher::resetReconnectState, this, &MainWindow::slotResetReconnectState);
0941 
0942     connect(imapModel(), &Imap::Mailbox::Model::mailboxFirstUnseenMessage, this, &MainWindow::slotScrollToUnseenMessage);
0943 
0944     connect(imapModel(), &Imap::Mailbox::Model::capabilitiesUpdated, this, &MainWindow::slotCapabilitiesUpdated);
0945 
0946     connect(m_imapAccess->msgListModel(), &QAbstractItemModel::modelReset, this, &MainWindow::slotUpdateWindowTitle);
0947     connect(imapModel(), &Imap::Mailbox::Model::messageCountPossiblyChanged, this, &MainWindow::slotUpdateWindowTitle);
0948 
0949     connect(prettyMsgListModel, &Imap::Mailbox::PrettyMsgListModel::sortingPreferenceChanged, this, &MainWindow::slotSortingConfirmed);
0950 
0951     //Imap::Mailbox::ModelWatcher* w = new Imap::Mailbox::ModelWatcher( this );
0952     //w->setModel( imapModel() );
0953 
0954     //ModelTest* tester = new ModelTest( prettyMboxModel, this ); // when testing, test just one model at time
0955 
0956     mboxTree->setModel(prettyMboxModel);
0957     msgListWidget->tree->setModel(prettyMsgListModel);
0958     connect(msgListWidget->tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateMessageFlags);
0959 
0960     allTree->setModel(imapModel());
0961     taskTree->setModel(imapModel()->taskModel());
0962     connect(imapModel()->taskModel(), &QAbstractItemModel::layoutChanged, taskTree, &QTreeView::expandAll);
0963     connect(imapModel()->taskModel(), &QAbstractItemModel::modelReset, taskTree, &QTreeView::expandAll);
0964     connect(imapModel()->taskModel(), &QAbstractItemModel::rowsInserted, taskTree, &QTreeView::expandAll);
0965     connect(imapModel()->taskModel(), &QAbstractItemModel::rowsRemoved, taskTree, &QTreeView::expandAll);
0966     connect(imapModel()->taskModel(), &QAbstractItemModel::rowsMoved, taskTree, &QTreeView::expandAll);
0967 
0968     busyParsersIndicator->setImapModel(imapModel());
0969 
0970     auto accountIconName = m_settings->value(Common::SettingsNames::imapAccountIcon).toString();
0971     if (accountIconName.isEmpty()) {
0972         qApp->setWindowIcon(UiUtils::loadIcon(QStringLiteral("trojita")));
0973     } else if (accountIconName.contains(QDir::separator())) {
0974         // Absolute paths are OK for users, but unsupported by our icon loader
0975         qApp->setWindowIcon(QIcon(accountIconName));
0976     } else {
0977         qApp->setWindowIcon(UiUtils::loadIcon(accountIconName));
0978     }
0979 }
0980 
0981 void MainWindow::createSysTray()
0982 {
0983     if (m_trayIcon)
0984         return;
0985 
0986     qApp->setQuitOnLastWindowClosed(false);
0987 
0988     m_trayIcon = new QSystemTrayIcon(this);
0989     handleTrayIconChange();
0990 
0991     QAction* quitAction = new QAction(tr("&Quit"), m_trayIcon);
0992     connect(quitAction, &QAction::triggered, qApp, &QApplication::quit);
0993 
0994     QMenu *trayIconMenu = new QMenu(this);
0995     trayIconMenu->addAction(quitAction);
0996     m_trayIcon->setContextMenu(trayIconMenu);
0997 
0998     // QMenu cannot be a child of QSystemTrayIcon, and we don't want the QMenu in MainWindow scope.
0999     connect(m_trayIcon, &QObject::destroyed, trayIconMenu, &QObject::deleteLater);
1000 
1001     connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::slotIconActivated);
1002     connect(imapModel(), &Imap::Mailbox::Model::messageCountPossiblyChanged, this, &MainWindow::handleTrayIconChange);
1003     m_trayIcon->setVisible(true);
1004     m_trayIcon->show();
1005 }
1006 
1007 void MainWindow::removeSysTray()
1008 {
1009     delete m_trayIcon;
1010     m_trayIcon = 0;
1011 
1012     qApp->setQuitOnLastWindowClosed(true);
1013 }
1014 
1015 void MainWindow::slotToggleSysTray()
1016 {
1017     bool showSystray = m_settings->value(Common::SettingsNames::guiShowSystray, QVariant(true)).toBool();
1018     if (showSystray && !m_trayIcon && QSystemTrayIcon::isSystemTrayAvailable()) {
1019         createSysTray();
1020     } else if (!showSystray && m_trayIcon) {
1021         removeSysTray();
1022     }
1023 }
1024 
1025 void MainWindow::handleTrayIconChange()
1026 {
1027     if (!m_trayIcon)
1028         return;
1029 
1030     const bool isOffline = qobject_cast<Imap::Mailbox::NetworkWatcher *>(m_imapAccess->networkWatcher())->effectiveNetworkPolicy()
1031             == Imap::Mailbox::NETWORK_OFFLINE;
1032     auto pixmap = qApp->windowIcon()
1033                 .pixmap(QSize(32, 32), isOffline ? QIcon::Disabled : QIcon::Normal);
1034     QString tooltip;
1035     auto profileName = QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
1036     if (profileName.isEmpty()) {
1037         tooltip = QStringLiteral("Trojitá");
1038     } else {
1039         tooltip = QStringLiteral("Trojitá [%1]").arg(profileName);
1040     }
1041 
1042     int unreadCount = 0;
1043     bool numbersValid = false;
1044 
1045     auto watchingMode = settings()->value(Common::SettingsNames::watchedFoldersKey).toString();
1046     if (watchingMode == Common::SettingsNames::watchAll || watchingMode == Common::SettingsNames::watchSubscribed) {
1047         bool subscribedOnly = watchingMode == Common::SettingsNames::watchSubscribed;
1048         unreadCount = std::accumulate(UiUtils::QaimDfsIterator(m_imapAccess->mailboxModel()->index(0, 0), m_imapAccess->mailboxModel()),
1049                                       UiUtils::QaimDfsIterator(), 0, [subscribedOnly](const int acc, const QModelIndex &idx) {
1050 
1051             if (subscribedOnly && !idx.data(Imap::Mailbox::RoleMailboxIsSubscribed).toBool())
1052                 return acc;
1053 
1054             auto x = idx.data(Imap::Mailbox::RoleUnreadMessageCount).toInt();
1055             if (x > 0) {
1056                 return acc + x;
1057             } else {
1058                 return acc;
1059             }
1060         });
1061         // only show stuff if there are some mailboxes, and if there are such messages
1062         numbersValid = m_imapAccess->mailboxModel()->hasChildren() && unreadCount > 0;
1063 
1064     } else {
1065         // just for the INBOX
1066         QModelIndex mailbox = imapModel()->index(1, 0, QModelIndex());
1067         if (mailbox.isValid() && mailbox.data(Imap::Mailbox::RoleMailboxName).toString() == QLatin1String("INBOX")
1068                 && mailbox.data(Imap::Mailbox::RoleUnreadMessageCount).toInt() > 0) {
1069             unreadCount = mailbox.data(Imap::Mailbox::RoleUnreadMessageCount).toInt();
1070             numbersValid = true;
1071         }
1072     }
1073 
1074     if (numbersValid) {
1075         QFont f;
1076         f.setPixelSize(pixmap.height() * 0.59);
1077         f.setWeight(QFont::Bold);
1078 
1079         QString text = QString::number(unreadCount);
1080         QFontMetrics fm(f);
1081         if (unreadCount > 666) {
1082             // You just have too many messages.
1083             text = QStringLiteral("🐮");
1084             fm = QFontMetrics(f);
1085         } else if (fm.horizontalAdvance(text) > pixmap.width()) {
1086             f.setPixelSize(f.pixelSize() * pixmap.width() / fm.horizontalAdvance(text));
1087             fm = QFontMetrics(f);
1088         }
1089 
1090         QRect boundingRect = fm.tightBoundingRect(text);
1091         boundingRect.setWidth(boundingRect.width() + 2);
1092         boundingRect.setHeight(boundingRect.height() + 2);
1093         boundingRect.moveCenter(QPoint(pixmap.width() / 2, pixmap.height() / 2));
1094         boundingRect = boundingRect.intersected(pixmap.rect());
1095 
1096         QPainterPath path;
1097         path.addText(boundingRect.bottomLeft(), f, text);
1098 
1099         QPainter painter(&pixmap);
1100         painter.setRenderHint(QPainter::Antialiasing);
1101         painter.setPen(QColor(255,255,255, 180));
1102         painter.setBrush(isOffline ? Qt::red : Qt::black);
1103         painter.drawPath(path);
1104 
1105         //: This is a tooltip for the tray icon. It will be prefixed by something like "Trojita" or "Trojita [work]"
1106         tooltip += tr(" - %n unread message(s)", 0, unreadCount);
1107     } else if (isOffline) {
1108         //: A tooltip suffix when offline. The prefix is something like "Trojita" or "Trojita [work]"
1109         tooltip += tr(" - offline");
1110     }
1111     m_trayIcon->setToolTip(tooltip);
1112     m_trayIcon->setIcon(QIcon(pixmap));
1113 }
1114 
1115 void MainWindow::closeEvent(QCloseEvent *event)
1116 {
1117     if (m_trayIcon && m_trayIcon->isVisible()) {
1118         Util::askForSomethingUnlessTold(tr("Trojitá"),
1119                                         tr("The application will continue in systray. This can be disabled within the settings."),
1120                                         Common::SettingsNames::guiOnSystrayClose, QMessageBox::Ok, this, m_settings);
1121         hide();
1122         event->ignore();
1123     }
1124 }
1125 
1126 bool MainWindow::eventFilter(QObject *o, QEvent *e)
1127 {
1128     if (msgListWidget && o == msgListWidget->tree && m_messageWidget->messageView) {
1129         if (e->type() == QEvent::KeyPress) {
1130             QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
1131             if (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Backspace) {
1132                 QCoreApplication::sendEvent(m_messageWidget, keyEvent);
1133                 return true;
1134             }
1135             return false;
1136         }
1137         return false;
1138     }
1139     if (msgListWidget && msgListWidget->tree && o == msgListWidget->tree->header()->viewport()) {
1140         // installed if sorting is not really possible.
1141         QWidget *header = static_cast<QWidget*>(o);
1142         QMouseEvent *mouse = static_cast<QMouseEvent*>(e);
1143         if (e->type() == QEvent::MouseButtonPress) {
1144             if (mouse->button() == Qt::LeftButton && header->cursor().shape() == Qt::ArrowCursor) {
1145                 m_headerDragStart = mouse->pos();
1146             }
1147             return false;
1148         }
1149         if (e->type() == QEvent::MouseButtonRelease) {
1150             if (mouse->button() == Qt::LeftButton && header->cursor().shape() == Qt::ArrowCursor &&
1151                (m_headerDragStart - mouse->pos()).manhattanLength() < QApplication::startDragDistance()) {
1152                     m_actionSortDescending->toggle();
1153                     Qt::SortOrder order = m_actionSortDescending->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder;
1154                     msgListWidget->tree->header()->setSortIndicator(-1, order);
1155                     return true; // prevent regular click
1156             }
1157         }
1158     }
1159     return false;
1160 }
1161 
1162 void MainWindow::slotIconActivated(const QSystemTrayIcon::ActivationReason reason)
1163 {
1164     if (reason == QSystemTrayIcon::Trigger) {
1165         setVisible(!isVisible());
1166         if (isVisible())
1167             showMainWindow();
1168     }
1169 }
1170 
1171 void MainWindow::showMainWindow()
1172 {
1173     setVisible(true);
1174     activateWindow();
1175     raise();
1176 }
1177 
1178 void MainWindow::msgListClicked(const QModelIndex &index)
1179 {
1180     Q_ASSERT(index.isValid());
1181 
1182     if (qApp->keyboardModifiers() & Qt::ShiftModifier || qApp->keyboardModifiers() & Qt::ControlModifier)
1183         return;
1184 
1185     if (! index.data(Imap::Mailbox::RoleMessageUid).isValid())
1186         return;
1187 
1188     // Because it's quite possible that we have switched into another mailbox, make sure that we're in the "current" one so that
1189     // user will be notified about new arrivals, etc.
1190     QModelIndex translated = Imap::deproxifiedIndex(index);
1191     imapModel()->switchToMailbox(translated.parent().parent());
1192 
1193     if (index.column() == Imap::Mailbox::MsgListModel::SEEN) {
1194         if (!translated.data(Imap::Mailbox::RoleIsFetched).toBool())
1195             return;
1196         Imap::Mailbox::FlagsOperation flagOp = translated.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool() ?
1197                                                Imap::Mailbox::FLAG_REMOVE : Imap::Mailbox::FLAG_ADD;
1198         imapModel()->markMessagesRead(QModelIndexList() << translated, flagOp);
1199 
1200         if (translated == m_messageWidget->messageView->currentMessage()) {
1201             m_messageWidget->messageView->stopAutoMarkAsRead();
1202         }
1203     } else if (index.column() == Imap::Mailbox::MsgListModel::FLAGGED) {
1204         if (!translated.data(Imap::Mailbox::RoleIsFetched).toBool())
1205             return;
1206 
1207         Imap::Mailbox::FlagsOperation flagOp = translated.data(Imap::Mailbox::RoleMessageIsMarkedFlagged).toBool() ?
1208                                                Imap::Mailbox::FLAG_REMOVE : Imap::Mailbox::FLAG_ADD;
1209         imapModel()->setMessageFlags(QModelIndexList() << translated, Imap::Mailbox::FlagNames::flagged, flagOp);
1210     } else {
1211         if ((m_messageWidget->isVisible() && !m_messageWidget->size().isEmpty()) || m_layoutMode == LAYOUT_ONE_AT_TIME) {
1212             // isVisible() won't work, the splitter manipulates width, not the visibility state
1213             m_messageWidget->messageView->setMessage(index);
1214         }
1215         msgListWidget->tree->setCurrentIndex(index);
1216     }
1217 }
1218 
1219 void MainWindow::openCompleteMessageWidget()
1220 {
1221     const QModelIndex index = msgListWidget->tree->currentIndex();
1222 
1223     if (! index.data(Imap::Mailbox::RoleMessageUid).isValid())
1224         return;
1225 
1226     CompleteMessageWidget *widget = new CompleteMessageWidget(0, m_settings, m_pluginManager, m_favoriteTags);
1227     widget->messageView->setMessage(index);
1228     widget->messageView->setNetworkWatcher(qobject_cast<Imap::Mailbox::NetworkWatcher*>(m_imapAccess->networkWatcher()));
1229     widget->setFocusPolicy(Qt::StrongFocus);
1230     widget->setWindowTitle(index.data(Imap::Mailbox::RoleMessageSubject).toString());
1231     widget->setAttribute(Qt::WA_DeleteOnClose);
1232     QAction *closeAction = ShortcutHandler::instance()->createAction(QStringLiteral("action_messagewindow_close"), widget, SLOT(close()), widget);
1233     widget->addAction(closeAction);
1234     widget->show();
1235 }
1236 
1237 void MainWindow::showContextMenuMboxTree(const QPoint &position)
1238 {
1239     QList<QAction *> actionList;
1240     if (mboxTree->indexAt(position).isValid()) {
1241         actionList.append(createChildMailbox);
1242         actionList.append(deleteCurrentMailbox);
1243         actionList.append(m_actionMarkMailboxAsRead);
1244         actionList.append(resyncMbox);
1245         actionList.append(reloadMboxList);
1246 
1247         actionList.append(m_actionSubscribeMailbox);
1248         m_actionSubscribeMailbox->setChecked(mboxTree->indexAt(position).data(Imap::Mailbox::RoleMailboxIsSubscribed).toBool());
1249     } else {
1250         actionList.append(createTopMailbox);
1251     }
1252     actionList.append(reloadAllMailboxes);
1253     actionList.append(m_actionShowOnlySubscribed);
1254     QMenu::exec(actionList, mboxTree->mapToGlobal(position), nullptr, this);
1255 }
1256 
1257 void MainWindow::showContextMenuMsgListTree(const QPoint &position)
1258 {
1259     QList<QAction *> actionList;
1260     QModelIndex index = msgListWidget->tree->indexAt(position);
1261     if (index.isValid()) {
1262         updateMessageFlagsOf(index);
1263         actionList.append(markAsRead);
1264         actionList.append(markAsDeleted);
1265         actionList.append(markAsFlagged);
1266         actionList.append(markAsJunk);
1267         actionList.append(markAsNotJunk);
1268         actionList.append(moveToArchive);
1269         actionList.append(m_actionMarkMailboxAsRead);
1270         actionList.append(saveWholeMessage);
1271         actionList.append(viewMsgSource);
1272         actionList.append(viewMsgHeaders);
1273         auto appendTagIfExists = [this,&actionList](const int row, QAction *tag) {
1274             if (m_favoriteTags->rowCount() > row - 1)
1275                 actionList.append(tag);
1276         };
1277         appendTagIfExists(1, tag1);
1278         appendTagIfExists(2, tag2);
1279         appendTagIfExists(3, tag3);
1280         appendTagIfExists(4, tag4);
1281         appendTagIfExists(5, tag5);
1282         appendTagIfExists(6, tag6);
1283         appendTagIfExists(7, tag7);
1284         appendTagIfExists(8, tag8);
1285         appendTagIfExists(9, tag9);
1286     }
1287     if (! actionList.isEmpty())
1288         QMenu::exec(actionList, msgListWidget->tree->mapToGlobal(position), nullptr, this);
1289 }
1290 
1291 /** @short Ask for an updated list of mailboxes situated below the selected one
1292 
1293 */
1294 void MainWindow::slotReloadMboxList()
1295 {
1296     Q_FOREACH(const QModelIndex &item, mboxTree->selectionModel()->selectedIndexes()) {
1297         Q_ASSERT(item.isValid());
1298         if (item.column() != 0)
1299             continue;
1300         Imap::Mailbox::TreeItemMailbox *mbox = dynamic_cast<Imap::Mailbox::TreeItemMailbox *>(
1301                 Imap::Mailbox::Model::realTreeItem(item)
1302                                                );
1303         Q_ASSERT(mbox);
1304         mbox->rescanForChildMailboxes(imapModel());
1305     }
1306 }
1307 
1308 /** @short Request a check for new messages in selected mailbox */
1309 void MainWindow::slotResyncMbox()
1310 {
1311     if (! imapModel()->isNetworkAvailable())
1312         return;
1313 
1314     Q_FOREACH(const QModelIndex &item, mboxTree->selectionModel()->selectedIndexes()) {
1315         Q_ASSERT(item.isValid());
1316         if (item.column() != 0)
1317             continue;
1318         imapModel()->resyncMailbox(item);
1319     }
1320 }
1321 
1322 void MainWindow::alertReceived(const QString &message)
1323 {
1324     //: "ALERT" is a special warning which we're required to show to the user
1325     Gui::Util::messageBoxWarning(this, tr("IMAP Alert"), message);
1326 }
1327 
1328 void MainWindow::imapError(const QString &message)
1329 {
1330     Gui::Util::messageBoxCritical(this, tr("IMAP Protocol Error"), message);
1331     // Show the IMAP logger -- maybe some user will take that as a hint that they shall include it in the bug report.
1332     // </joke>
1333     showProtocolLogger->setChecked(true);
1334 }
1335 
1336 void MainWindow::networkError(const QString &message)
1337 {
1338     const QString title = tr("Network Error");
1339     if (!m_networkErrorMessageBox) {
1340         m_networkErrorMessageBox = new QMessageBox(QMessageBox::Critical, title,
1341                                                    QString(), QMessageBox::Ok, this);
1342     }
1343     // User must be informed about a new (but not recurring) error
1344     if (message != m_networkErrorMessageBox->text()) {
1345         m_networkErrorMessageBox->setText(message);
1346         if (qApp->applicationState() == Qt::ApplicationActive) {
1347             m_networkErrorMessageBox->setProperty(netErrorUnseen, false);
1348             m_networkErrorMessageBox->show();
1349         } else {
1350             m_networkErrorMessageBox->setProperty(netErrorUnseen, true);
1351             if (m_trayIcon && m_trayIcon->isVisible())
1352                 m_trayIcon->showMessage(title, message, QSystemTrayIcon::Warning, 3333);
1353         }
1354     }
1355 }
1356 
1357 void MainWindow::cacheError(const QString &message)
1358 {
1359     Gui::Util::messageBoxCritical(this, tr("IMAP Cache Error"),
1360                                   tr("The caching subsystem managing a cache of the data already "
1361                                      "downloaded from the IMAP server is having troubles. "
1362                                      "All caching will be disabled.\n\n%1").arg(message));
1363 }
1364 
1365 void MainWindow::networkPolicyOffline()
1366 {
1367     netExpensive->setChecked(false);
1368     netOnline->setChecked(false);
1369     netOffline->setChecked(true);
1370     updateActionsOnlineOffline(false);
1371     showStatusMessage(tr("Offline"));
1372     handleTrayIconChange();
1373 }
1374 
1375 void MainWindow::networkPolicyExpensive()
1376 {
1377     netOffline->setChecked(false);
1378     netOnline->setChecked(false);
1379     netExpensive->setChecked(true);
1380     updateActionsOnlineOffline(true);
1381     handleTrayIconChange();
1382 }
1383 
1384 void MainWindow::networkPolicyOnline()
1385 {
1386     netOffline->setChecked(false);
1387     netExpensive->setChecked(false);
1388     netOnline->setChecked(true);
1389     updateActionsOnlineOffline(true);
1390     handleTrayIconChange();
1391 }
1392 
1393 /** @short Deletes a network error message box instance upon resetting of reconnect state */
1394 void MainWindow::slotResetReconnectState()
1395 {
1396     if (m_networkErrorMessageBox) {
1397         delete m_networkErrorMessageBox;
1398         m_networkErrorMessageBox = 0;
1399     }
1400 }
1401 
1402 void MainWindow::slotShowSettings()
1403 {
1404     SettingsDialog *dialog = new SettingsDialog(this, m_senderIdentities, m_favoriteTags, m_settings);
1405     if (dialog->exec() == QDialog::Accepted) {
1406         // FIXME: wipe cache in case we're moving between servers
1407         nukeModels();
1408         setupModels();
1409         connectModelActions();
1410         // The systray is still connected to the old model -- got to make sure it's getting updated
1411         removeSysTray();
1412         slotToggleSysTray();
1413     }
1414     QString method = m_settings->value(Common::SettingsNames::imapMethodKey).toString();
1415     if (method != Common::SettingsNames::methodTCP && method != Common::SettingsNames::methodSSL &&
1416             method != Common::SettingsNames::methodProcess ) {
1417         Gui::Util::messageBoxCritical(this, tr("No Configuration"),
1418                                       tr("No IMAP account is configured. Trojitá cannot do much without one."));
1419     }
1420     applySizesAndState();
1421 }
1422 
1423 void MainWindow::authenticationRequested()
1424 {
1425     Plugins::PasswordPlugin *password = pluginManager()->password();
1426     if (password) {
1427         // FIXME: use another account-id at some point in future
1428         //        Currently the accountName will be empty unless Trojita has been
1429         //        called with a profile, and then the profile will be used as the
1430         //        accountName.
1431         QString accountName = m_imapAccess->accountName();
1432         if (accountName.isEmpty())
1433             accountName = QStringLiteral("account-0");
1434         Plugins::PasswordJob *job = password->requestPassword(accountName, QStringLiteral("imap"));
1435         if (job) {
1436             connect(job, &Plugins::PasswordJob::passwordAvailable, this, [this](const QString &password) {
1437                 authenticationContinue(password);
1438             });
1439             connect(job, &Plugins::PasswordJob::error, this, [this](const Plugins::PasswordJob::Error error, const QString &message) {
1440                 if (error == Plugins::PasswordJob::Error::NoSuchPassword) {
1441                     authenticationContinue(QString());
1442                 } else {
1443                     authenticationContinue(QString(), tr("Failed to retrieve password from the store: %1").arg(message));
1444                 }
1445             });
1446             job->setAutoDelete(true);
1447             job->start();
1448             return;
1449         }
1450     }
1451 
1452     authenticationContinue(QString());
1453 
1454 }
1455 
1456 void MainWindow::authenticationContinue(const QString &password, const QString &errorMessage)
1457 {
1458     const QString &user = m_settings->value(Common::SettingsNames::imapUserKey).toString();
1459     QString pass = password;
1460     if (m_ignoreStoredPassword || pass.isEmpty()) {
1461         auto dialog = PasswordDialog::getPassword(this, tr("Authentication Required"),
1462                                                   tr("<p>Please provide IMAP password for user <b>%1</b> on <b>%2</b>:</p>").arg(
1463                                                       user.toHtmlEscaped(),
1464                                                       m_settings->value(Common::SettingsNames::imapHostKey).toString().toHtmlEscaped()
1465                                                       ),
1466                                                   errorMessage + (errorMessage.isEmpty() ? QString() : QStringLiteral("\n\n"))
1467                                                   + imapModel()->imapAuthError());
1468         connect(dialog, &PasswordDialog::gotPassword, imapModel(), &Imap::Mailbox::Model::setImapPassword);
1469         connect(dialog, &PasswordDialog::rejected, imapModel(), &Imap::Mailbox::Model::unsetImapPassword);
1470     } else {
1471         imapModel()->setImapPassword(pass);
1472     }
1473 }
1474 
1475 void MainWindow::checkSslPolicy()
1476 {
1477     m_imapAccess->setSslPolicy(QMessageBox(static_cast<QMessageBox::Icon>(m_imapAccess->sslInfoIcon()),
1478                                            m_imapAccess->sslInfoTitle(), m_imapAccess->sslInfoMessage(),
1479                                            QMessageBox::Yes | QMessageBox::No, this).exec() == QMessageBox::Yes);
1480 }
1481 
1482 void MainWindow::nukeModels()
1483 {
1484     m_messageWidget->messageView->setEmpty();
1485     mboxTree->setModel(0);
1486     msgListWidget->tree->setModel(0);
1487     allTree->setModel(0);
1488     taskTree->setModel(0);
1489     delete prettyMsgListModel;
1490     prettyMsgListModel = 0;
1491     delete prettyMboxModel;
1492     prettyMboxModel = 0;
1493 }
1494 
1495 void MainWindow::recoverDrafts()
1496 {
1497     QDir draftPath(Common::writablePath(Common::LOCATION_CACHE) + QLatin1String("Drafts/"));
1498     QStringList drafts(draftPath.entryList(QStringList() << QStringLiteral("*.draft")));
1499     Q_FOREACH(const QString &draft, drafts) {
1500         ComposeWidget *w = ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createDraft(this, draftPath.filePath(draft)), this);
1501         // No need to further try creating widgets for drafts if a nullptr is being returned by ComposeWidget::warnIfMsaNotConfigured
1502         if (!w)
1503             break;
1504     }
1505 }
1506 
1507 void MainWindow::slotComposeMail()
1508 {
1509     ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createBlank(this), this);
1510 }
1511 
1512 void MainWindow::slotEditDraft()
1513 {
1514     QString path(Common::writablePath(Common::LOCATION_DATA) + tr("Drafts"));
1515     QDir().mkpath(path);
1516     path = QFileDialog::getOpenFileName(this, tr("Edit draft"), path, tr("Drafts") + QLatin1String(" (*.draft)"));
1517     if (!path.isNull()) {
1518         ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createDraft(this, path), this);
1519     }
1520 }
1521 
1522 QModelIndexList MainWindow::translatedSelection() const
1523 {
1524     QModelIndexList translatedIndexes;
1525     Q_FOREACH(const QModelIndex &index, msgListWidget->tree->selectedTree()) {
1526         translatedIndexes << Imap::deproxifiedIndex(index);
1527     }
1528     return translatedIndexes;
1529 }
1530 
1531 void MainWindow::handleMarkAsRead(bool value)
1532 {
1533     const QModelIndexList translatedIndexes = translatedSelection();
1534     if (translatedIndexes.isEmpty()) {
1535         qDebug() << "Model::handleMarkAsRead: no valid messages";
1536     } else {
1537         imapModel()->markMessagesRead(translatedIndexes, value ? Imap::Mailbox::FLAG_ADD : Imap::Mailbox::FLAG_REMOVE);
1538         if (translatedIndexes.contains(m_messageWidget->messageView->currentMessage())) {
1539             m_messageWidget->messageView->stopAutoMarkAsRead();
1540         }
1541     }
1542 }
1543 
1544 void MainWindow::slotNextUnread()
1545 {
1546     QModelIndex current = msgListWidget->tree->currentIndex();
1547 
1548     UiUtils::gotoNext(msgListWidget->tree->model(), current,
1549     [](const QModelIndex &idx) { return !idx.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool(); },
1550     [this](const QModelIndex &idx) {
1551         Q_ASSERT(!idx.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool());
1552         m_messageWidget->messageView->setMessage(idx);
1553         msgListWidget->tree->setCurrentIndex(idx);
1554     },
1555     []() {
1556         // nothing to do
1557     });
1558 }
1559 
1560 void MainWindow::slotPreviousUnread()
1561 {
1562     QModelIndex current = msgListWidget->tree->currentIndex();
1563 
1564     UiUtils::gotoPrevious(msgListWidget->tree->model(), current,
1565     [](const QModelIndex &idx) { return !idx.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool(); },
1566     [this](const QModelIndex &idx) {
1567         Q_ASSERT(!idx.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool());
1568         m_messageWidget->messageView->setMessage(idx);
1569         msgListWidget->tree->setCurrentIndex(idx);
1570     },
1571     []() {
1572         // nothing to do
1573     });
1574 }
1575 
1576 void MainWindow::handleTag(const bool checked, const int index)
1577 {
1578     const QModelIndexList &translatedIndexes = translatedSelection();
1579     if (translatedIndexes.isEmpty()) {
1580         qDebug() << "Model::handleTag: no valid messages";
1581     } else {
1582         const auto &tagName = m_favoriteTags->tagNameByIndex(index);
1583         if (!tagName.isEmpty())
1584             imapModel()->setMessageFlags(translatedIndexes, tagName, checked ? Imap::Mailbox::FLAG_ADD : Imap::Mailbox::FLAG_REMOVE);
1585     }
1586 }
1587 
1588 void MainWindow::handleMarkAsDeleted(bool value)
1589 {
1590     const QModelIndexList translatedIndexes = translatedSelection();
1591     if (translatedIndexes.isEmpty()) {
1592         qDebug() << "Model::handleMarkAsDeleted: no valid messages";
1593     } else {
1594         imapModel()->markMessagesDeleted(translatedIndexes, value ? Imap::Mailbox::FLAG_ADD : Imap::Mailbox::FLAG_REMOVE);
1595     }
1596 }
1597 
1598 void MainWindow::handleMarkAsFlagged(const bool value)
1599 {
1600     const QModelIndexList translatedIndexes = translatedSelection();
1601     if (translatedIndexes.isEmpty()) {
1602         qDebug() << "Model::handleMarkAsFlagged: no valid messages";
1603     } else {
1604         imapModel()->setMessageFlags(translatedIndexes, Imap::Mailbox::FlagNames::flagged, value ? Imap::Mailbox::FLAG_ADD : Imap::Mailbox::FLAG_REMOVE);
1605     }
1606 }
1607 
1608 void MainWindow::handleMarkAsJunk(const bool value)
1609 {
1610     const QModelIndexList translatedIndexes = translatedSelection();
1611     if (translatedIndexes.isEmpty()) {
1612         qDebug() << "Model::handleMarkAsJunk: no valid messages";
1613     } else {
1614         if (value) {
1615             imapModel()->setMessageFlags(translatedIndexes, Imap::Mailbox::FlagNames::notjunk, Imap::Mailbox::FLAG_REMOVE);
1616         }
1617         imapModel()->setMessageFlags(translatedIndexes, Imap::Mailbox::FlagNames::junk, value ? Imap::Mailbox::FLAG_ADD : Imap::Mailbox::FLAG_REMOVE);
1618     }
1619 }
1620 
1621 void MainWindow::handleMarkAsNotJunk(const bool value)
1622 {
1623     const QModelIndexList translatedIndexes = translatedSelection();
1624     if (translatedIndexes.isEmpty()) {
1625         qDebug() << "Model::handleMarkAsNotJunk: no valid messages";
1626     } else {
1627         if (value) {
1628           imapModel()->setMessageFlags(translatedIndexes, Imap::Mailbox::FlagNames::junk, Imap::Mailbox::FLAG_REMOVE);
1629         }
1630         imapModel()->setMessageFlags(translatedIndexes, Imap::Mailbox::FlagNames::notjunk, value ? Imap::Mailbox::FLAG_ADD : Imap::Mailbox::FLAG_REMOVE);
1631     }
1632 }
1633 
1634 void MainWindow::slotMoveToArchiveFailed(const QString &error)
1635 {
1636     // XXX disable busy cursor
1637     QMessageBox::critical(this, tr("Failed to archive"), error);
1638 }
1639 
1640 void MainWindow::handleMoveToArchive()
1641 {
1642     const QModelIndexList translatedIndexes = translatedSelection();
1643     if (translatedIndexes.isEmpty()) {
1644         qDebug() << "Model::handleMoveToArchive: no valid messages";
1645     } else {
1646         auto archiveFolderName = m_settings->value(Common::SettingsNames::imapArchiveFolderName).toString();
1647         auto copyMoveMessagesTask = imapModel()->copyMoveMessages(
1648             archiveFolderName.isEmpty() ? Common::SettingsNames::imapDefaultArchiveFolderName : archiveFolderName,
1649             translatedIndexes, Imap::Mailbox::CopyMoveOperation::MOVE);
1650         connect(copyMoveMessagesTask, &Imap::Mailbox::ImapTask::failed, this, &MainWindow::slotMoveToArchiveFailed);
1651     }
1652 }
1653 
1654 
1655 void MainWindow::slotExpunge()
1656 {
1657     imapModel()->expungeMailbox(qobject_cast<Imap::Mailbox::MsgListModel *>(m_imapAccess->msgListModel())->currentMailbox());
1658 }
1659 
1660 void MainWindow::slotMarkCurrentMailboxRead()
1661 {
1662     imapModel()->markMailboxAsRead(mboxTree->currentIndex());
1663 }
1664 
1665 void MainWindow::slotCreateMailboxBelowCurrent()
1666 {
1667     createMailboxBelow(mboxTree->currentIndex());
1668 }
1669 
1670 void MainWindow::slotCreateTopMailbox()
1671 {
1672     createMailboxBelow(QModelIndex());
1673 }
1674 
1675 void MainWindow::createMailboxBelow(const QModelIndex &index)
1676 {
1677     Imap::Mailbox::TreeItemMailbox *mboxPtr = index.isValid() ?
1678             dynamic_cast<Imap::Mailbox::TreeItemMailbox *>(
1679                 Imap::Mailbox::Model::realTreeItem(index)) :
1680             0;
1681 
1682     Ui::CreateMailboxDialog ui;
1683     QDialog *dialog = new QDialog(this);
1684     ui.setupUi(dialog);
1685 
1686     dialog->setWindowTitle(mboxPtr ?
1687                            tr("Create a Subfolder of %1").arg(mboxPtr->mailbox()) :
1688                            tr("Create a Top-level Mailbox"));
1689 
1690     if (dialog->exec() == QDialog::Accepted) {
1691         QStringList parts;
1692         if (mboxPtr)
1693             parts << mboxPtr->mailbox();
1694         parts << ui.mailboxName->text();
1695         if (ui.otherMailboxes->isChecked())
1696             parts << QString();
1697         QString targetName = parts.join(mboxPtr ? mboxPtr->separator() : QString());   // FIXME: top-level separator
1698         imapModel()->createMailbox(targetName,
1699                                    ui.subscribe->isChecked() ?
1700                                        Imap::Mailbox::AutoSubscription::SUBSCRIBE :
1701                                        Imap::Mailbox::AutoSubscription::NO_EXPLICIT_SUBSCRIPTION
1702                                        );
1703     }
1704 }
1705 
1706 void MainWindow::slotDeleteCurrentMailbox()
1707 {
1708     if (! mboxTree->currentIndex().isValid())
1709         return;
1710 
1711     QModelIndex mailbox = Imap::deproxifiedIndex(mboxTree->currentIndex());
1712     Q_ASSERT(mailbox.isValid());
1713     QString name = mailbox.data(Imap::Mailbox::RoleMailboxName).toString();
1714 
1715     if (QMessageBox::question(this, tr("Delete Mailbox"),
1716                               tr("Are you sure to delete mailbox %1?").arg(name),
1717                               QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1718         imapModel()->deleteMailbox(name);
1719     }
1720 }
1721 
1722 void MainWindow::updateMessageFlags()
1723 {
1724     updateMessageFlagsOf(QModelIndex());
1725 }
1726 
1727 void MainWindow::updateMessageFlagsOf(const QModelIndex &index)
1728 {
1729     QModelIndexList indexes = index.isValid() ? QModelIndexList() << index : translatedSelection();
1730     const bool isValid = !indexes.isEmpty() &&
1731                          // either we operate on the -already valided- selection or the index must be valid
1732                          (!index.isValid() || index.data(Imap::Mailbox::RoleMessageUid).toUInt() > 0);
1733     const bool okToModify = imapModel()->isNetworkAvailable() && isValid;
1734 
1735     markAsRead->setEnabled(okToModify);
1736     markAsDeleted->setEnabled(okToModify);
1737     markAsFlagged->setEnabled(okToModify);
1738     markAsJunk->setEnabled(okToModify);
1739     markAsNotJunk->setEnabled(okToModify);
1740 
1741     // There's no point in moving from Archive to, well, Archive
1742     auto archiveFolderName = m_settings->value(Common::SettingsNames::imapArchiveFolderName).toString();
1743     if (archiveFolderName.isEmpty()) {
1744         archiveFolderName = Common::SettingsNames::imapDefaultArchiveFolderName;
1745     }
1746     moveToArchive->setEnabled(okToModify &&
1747                               std::any_of(indexes.cbegin(), indexes.cend(),
1748                                           [archiveFolderName](const QModelIndex &i) {
1749         return i.data(Imap::Mailbox::RoleMailboxName) != archiveFolderName;
1750     }));
1751 
1752     tag1->setEnabled(okToModify);
1753     tag2->setEnabled(okToModify);
1754     tag3->setEnabled(okToModify);
1755     tag4->setEnabled(okToModify);
1756     tag5->setEnabled(okToModify);
1757     tag6->setEnabled(okToModify);
1758     tag7->setEnabled(okToModify);
1759     tag8->setEnabled(okToModify);
1760     tag9->setEnabled(okToModify);
1761 
1762     bool isRead    = isValid,
1763          isDeleted = isValid,
1764          isFlagged = isValid,
1765          isJunk    = isValid,
1766          isNotJunk = isValid,
1767          hasTag1   = isValid,
1768          hasTag2   = isValid,
1769          hasTag3   = isValid,
1770          hasTag4   = isValid,
1771          hasTag5   = isValid,
1772          hasTag6   = isValid,
1773          hasTag7   = isValid,
1774          hasTag8   = isValid,
1775          hasTag9   = isValid;
1776     auto updateTag = [=](const QModelIndex &i, bool &hasTag, int index) {
1777         if (hasTag && !m_favoriteTags->tagNameByIndex(index).isEmpty() &&
1778                 !i.data(Imap::Mailbox::RoleMessageFlags).toStringList().contains(m_favoriteTags->tagNameByIndex(index)))
1779         {
1780             hasTag = false;
1781         }
1782     };
1783     Q_FOREACH (const QModelIndex &i, indexes) {
1784 #define UPDATE_STATE(PROP) \
1785         if (is##PROP && !i.data(Imap::Mailbox::RoleMessageIsMarked##PROP).toBool()) \
1786             is##PROP = false;
1787         UPDATE_STATE(Read)
1788         UPDATE_STATE(Deleted)
1789         UPDATE_STATE(Flagged)
1790         UPDATE_STATE(Junk)
1791         UPDATE_STATE(NotJunk)
1792 #undef UPDATE_STATE
1793         updateTag(i, hasTag1, 0);
1794         updateTag(i, hasTag2, 1);
1795         updateTag(i, hasTag3, 2);
1796         updateTag(i, hasTag4, 3);
1797         updateTag(i, hasTag5, 4);
1798         updateTag(i, hasTag6, 5);
1799         updateTag(i, hasTag7, 6);
1800         updateTag(i, hasTag8, 7);
1801         updateTag(i, hasTag9, 8);
1802     }
1803     markAsRead->setChecked(isRead);
1804     markAsDeleted->setChecked(isDeleted);
1805     markAsFlagged->setChecked(isFlagged);
1806     markAsJunk->setChecked(isJunk && !isNotJunk);
1807     markAsNotJunk->setChecked(isNotJunk && !isJunk);
1808 
1809     tag1->setChecked(hasTag1);
1810     tag2->setChecked(hasTag2);
1811     tag3->setChecked(hasTag3);
1812     tag4->setChecked(hasTag4);
1813     tag5->setChecked(hasTag5);
1814     tag6->setChecked(hasTag6);
1815     tag7->setChecked(hasTag7);
1816     tag8->setChecked(hasTag8);
1817     tag9->setChecked(hasTag9);
1818 }
1819 
1820 void MainWindow::updateActionsOnlineOffline(bool online)
1821 {
1822     reloadMboxList->setEnabled(online);
1823     resyncMbox->setEnabled(online);
1824     expunge->setEnabled(online);
1825     createChildMailbox->setEnabled(online);
1826     createTopMailbox->setEnabled(online);
1827     deleteCurrentMailbox->setEnabled(online);
1828     m_actionMarkMailboxAsRead->setEnabled(online);
1829     updateMessageFlags();
1830     showImapCapabilities->setEnabled(online);
1831     if (!online) {
1832         m_replyGuess->setEnabled(false);
1833         m_replyPrivate->setEnabled(false);
1834         m_replyAll->setEnabled(false);
1835         m_replyAllButMe->setEnabled(false);
1836         m_replyList->setEnabled(false);
1837         m_forwardAsAttachment->setEnabled(false);
1838         m_resend->setEnabled(false);
1839     }
1840 }
1841 
1842 void MainWindow::slotUpdateMessageActions()
1843 {
1844     Composer::RecipientList dummy;
1845     m_replyPrivate->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_PRIVATE, senderIdentitiesModel(),
1846                                                                   m_messageWidget->messageView->currentMessage(), dummy));
1847     m_replyAllButMe->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_ALL_BUT_ME, senderIdentitiesModel(),
1848                                                                    m_messageWidget->messageView->currentMessage(), dummy));
1849     m_replyAll->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_ALL, senderIdentitiesModel(),
1850                                                               m_messageWidget->messageView->currentMessage(), dummy));
1851     m_replyList->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_LIST, senderIdentitiesModel(),
1852                                                                m_messageWidget->messageView->currentMessage(), dummy));
1853     m_replyGuess->setEnabled(m_replyPrivate->isEnabled() || m_replyAllButMe->isEnabled()
1854                              || m_replyAll->isEnabled() || m_replyList->isEnabled());
1855 
1856     // Check the default reply mode
1857     // I suspect this is not going to work for everybody. Suggestions welcome...
1858     if (m_replyList->isEnabled()) {
1859         m_replyButton->setDefaultAction(m_replyList);
1860     } else if (m_replyAllButMe->isEnabled()) {
1861         m_replyButton->setDefaultAction(m_replyAllButMe);
1862     } else {
1863         m_replyButton->setDefaultAction(m_replyPrivate);
1864     }
1865 
1866     m_forwardAsAttachment->setEnabled(m_messageWidget->messageView->currentMessage().isValid());
1867     m_resend->setEnabled(m_messageWidget->messageView->currentMessage().isValid());
1868 }
1869 
1870 void MainWindow::scrollMessageUp()
1871 {
1872     m_messageWidget->area->ensureVisible(0, 0, 0, 0);
1873 }
1874 
1875 void MainWindow::slotReplyTo()
1876 {
1877     m_messageWidget->messageView->reply(this, Composer::REPLY_PRIVATE);
1878 }
1879 
1880 void MainWindow::slotReplyAll()
1881 {
1882     m_messageWidget->messageView->reply(this, Composer::REPLY_ALL);
1883 }
1884 
1885 void MainWindow::slotReplyAllButMe()
1886 {
1887     m_messageWidget->messageView->reply(this, Composer::REPLY_ALL_BUT_ME);
1888 }
1889 
1890 void MainWindow::slotReplyList()
1891 {
1892     m_messageWidget->messageView->reply(this, Composer::REPLY_LIST);
1893 }
1894 
1895 void MainWindow::slotReplyGuess()
1896 {
1897     if (m_replyButton->defaultAction() == m_replyAllButMe) {
1898         slotReplyAllButMe();
1899     } else if (m_replyButton->defaultAction() == m_replyAll) {
1900         slotReplyAll();
1901     } else if (m_replyButton->defaultAction() == m_replyList) {
1902         slotReplyList();
1903     } else {
1904         slotReplyTo();
1905     }
1906 }
1907 
1908 void MainWindow::slotForwardAsAttachment()
1909 {
1910     m_messageWidget->messageView->forward(this, Composer::ForwardMode::FORWARD_AS_ATTACHMENT);
1911 }
1912 
1913 void MainWindow::slotResend()
1914 {
1915     QModelIndex index;
1916     Imap::Mailbox::Model::realTreeItem(msgListWidget->tree->currentIndex(), nullptr, &index);
1917     if (!index.isValid())
1918         return;
1919 
1920     auto recipients = QList<QPair<Composer::RecipientKind,QString>>();
1921     for (const auto &kind: {Imap::Mailbox::RoleMessageTo, Imap::Mailbox::RoleMessageCc, Imap::Mailbox::RoleMessageBcc}) {
1922         for (const auto &oneAddr : index.data(kind).toList()) {
1923             Q_ASSERT(oneAddr.type() == QVariant::StringList);
1924             QStringList item = oneAddr.toStringList();
1925             Q_ASSERT(item.size() == 4);
1926             Imap::Message::MailAddress a(item[0], item[1], item[2], item[3]);
1927             Composer::RecipientKind translatedKind = Composer::RecipientKind::ADDRESS_TO;
1928             switch (kind) {
1929             case Imap::Mailbox::RoleMessageTo:
1930                 translatedKind = Composer::RecipientKind::ADDRESS_RESENT_TO;
1931                 break;
1932             case Imap::Mailbox::RoleMessageCc:
1933                 translatedKind = Composer::RecipientKind::ADDRESS_RESENT_CC;
1934                 break;
1935             case Imap::Mailbox::RoleMessageBcc:
1936                 translatedKind = Composer::RecipientKind::ADDRESS_RESENT_BCC;
1937                 break;
1938             default:
1939                 Q_ASSERT(false);
1940                 break;
1941             }
1942             recipients.push_back({translatedKind, a.asPrettyString()});
1943         }
1944     }
1945 
1946     ComposeWidget::warnIfMsaNotConfigured(
1947                 ComposeWidget::createFromReadOnly(this, index, recipients),
1948                 this);
1949 
1950 }
1951 
1952 void MainWindow::slotComposeMailUrl(const QUrl &url)
1953 {
1954     ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createFromUrl(this, url), this);
1955 }
1956 
1957 void MainWindow::slotManageContact(const QUrl &url)
1958 {
1959     Imap::Message::MailAddress addr;
1960     if (!Imap::Message::MailAddress::fromUrl(addr, url, QStringLiteral("x-trojita-manage-contact")))
1961         return;
1962 
1963     Plugins::AddressbookPlugin *addressbook = pluginManager()->addressbook();
1964     if (!addressbook)
1965         return;
1966 
1967     addressbook->openContactWindow(addr.mailbox + QLatin1Char('@') + addr.host, addr.name);
1968 }
1969 
1970 void MainWindow::invokeContactEditor()
1971 {
1972     Plugins::AddressbookPlugin *addressbook = pluginManager()->addressbook();
1973     if (!addressbook)
1974         return;
1975 
1976     addressbook->openAddressbookWindow();
1977 }
1978 
1979 /** @short Create an MSAFactory as per the settings */
1980 MSA::MSAFactory *MainWindow::msaFactory()
1981 {
1982     using namespace Common;
1983     QString method = m_settings->value(SettingsNames::msaMethodKey).toString();
1984     MSA::MSAFactory *msaFactory = 0;
1985     if (method == SettingsNames::methodSMTP || method == SettingsNames::methodSSMTP) {
1986         msaFactory = new MSA::SMTPFactory(m_settings->value(SettingsNames::smtpHostKey).toString(),
1987                                           m_settings->value(SettingsNames::smtpPortKey).toInt(),
1988                                           (method == SettingsNames::methodSSMTP),
1989                                           (method == SettingsNames::methodSMTP)
1990                                           && m_settings->value(SettingsNames::smtpStartTlsKey).toBool(),
1991                                           m_settings->value(SettingsNames::smtpAuthKey).toBool(),
1992                                           m_settings->value(SettingsNames::smtpAuthReuseImapCredsKey, false).toBool() ?
1993                                               m_settings->value(SettingsNames::imapUserKey).toString() :
1994                                               m_settings->value(SettingsNames::smtpUserKey).toString());
1995     } else if (method == SettingsNames::methodSENDMAIL) {
1996         QStringList args = m_settings->value(SettingsNames::sendmailKey, SettingsNames::sendmailDefaultCmd).toString().split(QLatin1Char(' '));
1997         if (args.isEmpty()) {
1998             return 0;
1999         }
2000         QString appName = args.takeFirst();
2001         msaFactory = new MSA::SendmailFactory(appName, args);
2002     } else if (method == SettingsNames::methodImapSendmail) {
2003         if (!imapModel()->capabilities().contains(QStringLiteral("X-DRAFT-I01-SENDMAIL"))) {
2004             return 0;
2005         }
2006         msaFactory = new MSA::ImapSubmitFactory(qobject_cast<Imap::Mailbox::Model*>(imapAccess()->imapModel()));
2007     } else {
2008         return 0;
2009     }
2010     return msaFactory;
2011 }
2012 
2013 void MainWindow::slotMailboxDeleteFailed(const QString &mailbox, const QString &msg)
2014 {
2015     Gui::Util::messageBoxWarning(this, tr("Can't delete mailbox"),
2016                                  tr("Deleting mailbox \"%1\" failed with the following message:\n%2").arg(mailbox, msg));
2017 }
2018 
2019 void MainWindow::slotMailboxCreateFailed(const QString &mailbox, const QString &msg)
2020 {
2021     Gui::Util::messageBoxWarning(this, tr("Can't create mailbox"),
2022                                  tr("Creating mailbox \"%1\" failed with the following message:\n%2").arg(mailbox, msg));
2023 }
2024 
2025 void MainWindow::slotMailboxSyncFailed(const QString &mailbox, const QString &msg)
2026 {
2027     Gui::Util::messageBoxWarning(this, tr("Can't open mailbox"),
2028                                  tr("Opening mailbox \"%1\" failed with the following message:\n%2").arg(mailbox, msg));
2029 }
2030 
2031 void MainWindow::slotMailboxChanged(const QModelIndex &mailbox)
2032 {
2033     using namespace Imap::Mailbox;
2034     QString mailboxName = mailbox.data(RoleMailboxName).toString();
2035     bool isSentMailbox = mailbox.isValid() && !mailboxName.isEmpty() &&
2036             m_settings->value(Common::SettingsNames::composerSaveToImapKey).toBool() &&
2037             mailboxName == m_settings->value(Common::SettingsNames::composerImapSentKey).toString();
2038     QTreeView *tree = msgListWidget->tree;
2039 
2040     // Automatically trigger visibility of the TO and FROM columns
2041     if (isSentMailbox) {
2042         if (tree->isColumnHidden(MsgListModel::TO) && !tree->isColumnHidden(MsgListModel::FROM)) {
2043             tree->hideColumn(MsgListModel::FROM);
2044             tree->showColumn(MsgListModel::TO);
2045         }
2046     } else {
2047         if (tree->isColumnHidden(MsgListModel::FROM) && !tree->isColumnHidden(MsgListModel::TO)) {
2048             tree->hideColumn(MsgListModel::TO);
2049             tree->showColumn(MsgListModel::FROM);
2050         }
2051     }
2052 
2053     updateMessageFlags();
2054     slotScrollToUnseenMessage();
2055 }
2056 
2057 void MainWindow::showConnectionStatus(uint parserId, Imap::ConnectionState state)
2058 {
2059     Q_UNUSED(parserId);
2060     static Imap::ConnectionState previousState = Imap::ConnectionState::CONN_STATE_NONE;
2061     QString message = connectionStateToString(state);
2062 
2063     if (state == Imap::ConnectionState::CONN_STATE_SELECTED && previousState >= Imap::ConnectionState::CONN_STATE_SELECTED) {
2064         // prevent excessive popups when we "reset the state" to something which is shown quite often
2065         showStatusMessage(QString());
2066     } else {
2067         showStatusMessage(message);
2068     }
2069     previousState = state;
2070 }
2071 
2072 void MainWindow::slotShowLinkTarget(const QString &link)
2073 {
2074     if (link.isEmpty()) {
2075         QToolTip::hideText();
2076     } else {
2077         QToolTip::showText(QCursor::pos(), tr("Link target: %1").arg(UiUtils::Formatting::htmlEscaped(link)));
2078     }
2079 }
2080 
2081 void MainWindow::slotShowAboutTrojita()
2082 {
2083     Ui::AboutDialog ui;
2084     QDialog *widget = new QDialog(this);
2085     widget->setAttribute(Qt::WA_DeleteOnClose);
2086     ui.setupUi(widget);
2087     ui.versionLabel->setText(Common::Application::version);
2088     ui.qtVersion->setText(QStringLiteral("<a href=\"about-qt\">Qt " QT_VERSION_STR "</a>"));
2089     connect(ui.qtVersion, &QLabel::linkActivated, qApp, &QApplication::aboutQt);
2090 
2091     std::vector<std::pair<QString, bool>> features;
2092     features.emplace_back(tr("Plugins"),
2093 #ifdef WITH_SHARED_PLUGINS
2094             true
2095 #else
2096             false
2097 #endif
2098                        );
2099     features.emplace_back(tr("Encrypted and signed messages"),
2100 #ifdef TROJITA_HAVE_CRYPTO_MESSAGES
2101             true
2102 #else
2103             false
2104 #endif
2105                        );
2106     features.emplace_back(tr("IMAP compression"),
2107 #ifdef TROJITA_HAVE_ZLIB
2108             true
2109 #else
2110             false
2111 #endif
2112                        );
2113 
2114     QString featuresText = QStringLiteral("<ul>");
2115     for (const auto &x: features) {
2116         featuresText += x.second ?
2117                     tr("<li>%1: supported</li>").arg(x.first)
2118                   : tr("<li>%1: <strong>disabled</strong></li>").arg(x.first);
2119     }
2120     featuresText += QStringLiteral("</ul>");
2121     ui.descriptionLabel->setText(ui.descriptionLabel->text() + featuresText);
2122 
2123     QStringList copyright;
2124     {
2125         // Find the names of the authors and remove date codes from there
2126         QFile license(QStringLiteral(":/LICENSE"));
2127         license.open(QFile::ReadOnly);
2128         const QString prefix(QStringLiteral("Copyright (C) "));
2129         Q_FOREACH(const QString &line, QString::fromUtf8(license.readAll()).split(QLatin1Char('\n'))) {
2130             if (line.startsWith(prefix)) {
2131                 const int pos = prefix.size();
2132                 copyright << QChar(0xa9 /* COPYRIGHT SIGN */) + QLatin1Char(' ') +
2133                              line.mid(pos).replace(QRegularExpression(QLatin1String("(\\d) - (\\d)")),
2134                                                    QLatin1String("\\1") + QChar(0x2014 /* EM DASH */) + QLatin1String("\\2"));
2135             }
2136         }
2137     }
2138     ui.credits->setTextFormat(Qt::PlainText);
2139     ui.credits->setText(copyright.join(QStringLiteral("\n")));
2140     widget->show();
2141 }
2142 
2143 void MainWindow::slotDonateToTrojita()
2144 {
2145     QDesktopServices::openUrl(QStringLiteral("https://sourceforge.net/p/trojita/donate/"));
2146 }
2147 
2148 void MainWindow::slotSaveCurrentMessageBody()
2149 {
2150     Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
2151         Q_ASSERT(item.isValid());
2152         if (item.column() != 0)
2153             continue;
2154         if (! item.data(Imap::Mailbox::RoleMessageUid).isValid())
2155             continue;
2156 
2157         QModelIndex messageIndex = Imap::deproxifiedIndex(item);
2158 
2159         Imap::Network::MsgPartNetAccessManager *netAccess = new Imap::Network::MsgPartNetAccessManager(this);
2160         netAccess->setModelMessage(messageIndex);
2161         Imap::Network::FileDownloadManager *fileDownloadManager =
2162             new Imap::Network::FileDownloadManager(this, netAccess, messageIndex);
2163         connect(fileDownloadManager, &Imap::Network::FileDownloadManager::succeeded, fileDownloadManager, &QObject::deleteLater);
2164         connect(fileDownloadManager, &Imap::Network::FileDownloadManager::transferError, fileDownloadManager, &QObject::deleteLater);
2165         connect(fileDownloadManager, &Imap::Network::FileDownloadManager::fileNameRequested,
2166                 this, &MainWindow::slotDownloadMessageFileNameRequested);
2167         connect(fileDownloadManager, &Imap::Network::FileDownloadManager::transferError,
2168                 this, &MainWindow::slotDownloadTransferError);
2169         connect(fileDownloadManager, &QObject::destroyed, netAccess, &QObject::deleteLater);
2170         fileDownloadManager->downloadMessage();
2171     }
2172 }
2173 
2174 void MainWindow::slotDownloadTransferError(const QString &errorString)
2175 {
2176     Gui::Util::messageBoxCritical(this, tr("Can't save into file"),
2177                                   tr("Unable to save into file. Error:\n%1").arg(errorString));
2178 }
2179 
2180 void MainWindow::slotDownloadMessageFileNameRequested(QString *fileName)
2181 {
2182     *fileName = QFileDialog::getSaveFileName(this, tr("Save Message"), *fileName, QString(), 0,
2183                                              QFileDialog::HideNameFilterDetails);
2184 }
2185 
2186 void MainWindow::slotViewMsgSource()
2187 {
2188     Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
2189         Q_ASSERT(item.isValid());
2190         if (item.column() != 0)
2191             continue;
2192         if (! item.data(Imap::Mailbox::RoleMessageUid).isValid())
2193             continue;
2194         auto w = messageSourceWidget(item);
2195         //: Translators: %1 is the UID of a message (a number) and %2 is the name of a mailbox.
2196         w->setWindowTitle(tr("Message source of UID %1 in %2").arg(
2197                               QString::number(item.data(Imap::Mailbox::RoleMessageUid).toUInt()),
2198                               Imap::deproxifiedIndex(item).parent().parent().data(Imap::Mailbox::RoleMailboxName).toString()
2199                               ));
2200         w->show();
2201     }
2202 }
2203 
2204 QWidget *MainWindow::messageSourceWidget(const QModelIndex &message)
2205 {
2206     QModelIndex messageIndex = Imap::deproxifiedIndex(message);
2207     MessageSourceWidget *sourceWidget = new MessageSourceWidget(0, messageIndex);
2208     sourceWidget->setAttribute(Qt::WA_DeleteOnClose);
2209     QAction *close = new QAction(UiUtils::loadIcon(QStringLiteral("window-close")), tr("Close"), sourceWidget);
2210     sourceWidget->addAction(close);
2211     close->setShortcut(tr("Ctrl+W"));
2212     connect(close, &QAction::triggered, sourceWidget, &QWidget::close);
2213     return sourceWidget;
2214 }
2215 
2216 void MainWindow::slotViewMsgHeaders()
2217 {
2218     Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
2219         Q_ASSERT(item.isValid());
2220         if (item.column() != 0)
2221             continue;
2222         if (! item.data(Imap::Mailbox::RoleMessageUid).isValid())
2223             continue;
2224         QModelIndex messageIndex = Imap::deproxifiedIndex(item);
2225 
2226         auto widget = new MessageHeadersWidget(nullptr, messageIndex);
2227         widget->setAttribute(Qt::WA_DeleteOnClose);
2228         QAction *close = new QAction(UiUtils::loadIcon(QStringLiteral("window-close")), tr("Close"), widget);
2229         widget->addAction(close);
2230         close->setShortcut(tr("Ctrl+W"));
2231         connect(close, &QAction::triggered, widget, &QWidget::close);
2232         widget->show();
2233     }
2234 }
2235 
2236 void MainWindow::slotSubscribeCurrentMailbox()
2237 {
2238     QModelIndex index = mboxTree->currentIndex();
2239     if (! index.isValid())
2240         return;
2241 
2242     QString mailbox = index.data(Imap::Mailbox::RoleMailboxName).toString();
2243     if (m_actionSubscribeMailbox->isChecked()) {
2244         imapModel()->subscribeMailbox(mailbox);
2245     } else {
2246         imapModel()->unsubscribeMailbox(mailbox);
2247     }
2248 }
2249 
2250 void MainWindow::slotShowOnlySubscribed()
2251 {
2252     if (m_actionShowOnlySubscribed->isEnabled()) {
2253         m_settings->setValue(Common::SettingsNames::guiMailboxListShowOnlySubscribed, m_actionShowOnlySubscribed->isChecked());
2254         prettyMboxModel->setShowOnlySubscribed(m_actionShowOnlySubscribed->isChecked());
2255     }
2256 }
2257 
2258 void MainWindow::slotScrollToUnseenMessage()
2259 {
2260     // Now this is much, much more reliable than messing around with finding out an "interesting message"...
2261     if (!m_actionSortNone->isChecked() && !m_actionSortThreading->isChecked()) {
2262         // we're using some funky sorting, better don't scroll anywhere
2263     }
2264     if (m_actionSortDescending->isChecked()) {
2265         msgListWidget->tree->scrollToTop();
2266     } else {
2267         msgListWidget->tree->scrollToBottom();
2268     }
2269 }
2270 
2271 void MainWindow::slotScrollToCurrent()
2272 {
2273     // TODO: begs for lambda
2274     if (QScrollBar *vs = msgListWidget->tree->verticalScrollBar()) {
2275         vs->setValue(vs->maximum() - vs->value()); // implies vs->minimum() == 0
2276     }
2277 }
2278 
2279 void MainWindow::slotThreadMsgList()
2280 {
2281     // We want to save user's preferences and not override them with "threading disabled" when the server
2282     // doesn't report them, like in initial greetings. That's why we have to check for isEnabled() here.
2283     const bool useThreading = actionThreadMsgList->isChecked();
2284 
2285     // Switching between threaded/unthreaded view shall reset the sorting criteria. The goal is to make
2286     // sorting rather seldomly used as people shall instead use proper threading.
2287     if (useThreading) {
2288         m_actionSortThreading->setEnabled(true);
2289         if (!m_actionSortThreading->isChecked())
2290             m_actionSortThreading->trigger();
2291         m_actionSortNone->setEnabled(false);
2292     } else {
2293         m_actionSortNone->setEnabled(true);
2294         if (!m_actionSortNone->isChecked())
2295             m_actionSortNone->trigger();
2296         m_actionSortThreading->setEnabled(false);
2297     }
2298 
2299     QPersistentModelIndex currentItem = msgListWidget->tree->currentIndex();
2300 
2301     if (useThreading && actionThreadMsgList->isEnabled()) {
2302         msgListWidget->tree->setRootIsDecorated(true);
2303         qobject_cast<Imap::Mailbox::ThreadingMsgListModel *>(m_imapAccess->threadingMsgListModel())->setUserWantsThreading(true);
2304     } else {
2305         msgListWidget->tree->setRootIsDecorated(false);
2306         qobject_cast<Imap::Mailbox::ThreadingMsgListModel *>(m_imapAccess->threadingMsgListModel())->setUserWantsThreading(false);
2307     }
2308     m_settings->setValue(Common::SettingsNames::guiMsgListShowThreading, QVariant(useThreading));
2309 
2310     if (currentItem.isValid()) {
2311         msgListWidget->tree->scrollTo(currentItem);
2312     } else {
2313         // If we cannot determine the current item, at least scroll to a predictable place. Without this, the view
2314         // would jump to "weird" places, probably due to some heuristics about trying to show "roughly the same"
2315         // objects as what was visible before the reshuffling.
2316         slotScrollToUnseenMessage();
2317     }
2318 }
2319 
2320 void MainWindow::slotSortingPreferenceChanged()
2321 {
2322     Qt::SortOrder order = m_actionSortDescending->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder;
2323 
2324     using namespace Imap::Mailbox;
2325 
2326     int column = -1;
2327     if (m_actionSortByArrival->isChecked()) {
2328         column = MsgListModel::RECEIVED_DATE;
2329     } else if (m_actionSortByCc->isChecked()) {
2330         column = MsgListModel::CC;
2331     } else if (m_actionSortByDate->isChecked()) {
2332         column = MsgListModel::DATE;
2333     } else if (m_actionSortByFrom->isChecked()) {
2334         column = MsgListModel::FROM;
2335     } else if (m_actionSortBySize->isChecked()) {
2336         column = MsgListModel::SIZE;
2337     } else if (m_actionSortBySubject->isChecked()) {
2338         column = MsgListModel::SUBJECT;
2339     } else if (m_actionSortByTo->isChecked()) {
2340         column = MsgListModel::TO;
2341     } else {
2342         column = -1;
2343     }
2344 
2345     msgListWidget->tree->header()->setSortIndicator(column, order);
2346 }
2347 
2348 void MainWindow::slotSortingConfirmed(int column, Qt::SortOrder order)
2349 {
2350     // don't do anything during initialization
2351     if (!m_actionSortNone)
2352         return;
2353 
2354     using namespace Imap::Mailbox;
2355     QAction *action;
2356 
2357     switch (column) {
2358     case MsgListModel::SEEN:
2359     case MsgListModel::FLAGGED:
2360     case MsgListModel::ATTACHMENT:
2361     case MsgListModel::COLUMN_COUNT:
2362     case MsgListModel::BCC:
2363     case -1:
2364         if (actionThreadMsgList->isChecked())
2365             action = m_actionSortThreading;
2366         else
2367             action = m_actionSortNone;
2368         break;
2369     case MsgListModel::SUBJECT:
2370         action = m_actionSortBySubject;
2371         break;
2372     case MsgListModel::FROM:
2373         action = m_actionSortByFrom;
2374         break;
2375     case MsgListModel::TO:
2376         action = m_actionSortByTo;
2377         break;
2378     case MsgListModel::CC:
2379         action = m_actionSortByCc;
2380         break;
2381     case MsgListModel::DATE:
2382         action = m_actionSortByDate;
2383         break;
2384     case MsgListModel::RECEIVED_DATE:
2385         action = m_actionSortByArrival;
2386         break;
2387     case MsgListModel::SIZE:
2388         action = m_actionSortBySize;
2389         break;
2390     default:
2391         action = m_actionSortNone;
2392     }
2393 
2394     action->setChecked(true);
2395     if (order == Qt::DescendingOrder)
2396         m_actionSortDescending->setChecked(true);
2397     else
2398         m_actionSortAscending->setChecked(true);
2399 }
2400 
2401 void MainWindow::slotSearchRequested(const QStringList &searchConditions)
2402 {
2403     Imap::Mailbox::ThreadingMsgListModel * threadingMsgListModel =
2404             qobject_cast<Imap::Mailbox::ThreadingMsgListModel *>(m_imapAccess->threadingMsgListModel());
2405     threadingMsgListModel->setUserSearchingSortingPreference(searchConditions, threadingMsgListModel->currentSortCriterium(),
2406                                                              threadingMsgListModel->currentSortOrder());
2407 }
2408 
2409 void MainWindow::slotHideRead()
2410 {
2411     const bool hideRead = actionHideRead->isChecked();
2412     prettyMsgListModel->setHideRead(hideRead);
2413     m_settings->setValue(Common::SettingsNames::guiMsgListHideRead, QVariant(hideRead));
2414 }
2415 
2416 void MainWindow::slotCapabilitiesUpdated(const QStringList &capabilities)
2417 {
2418     msgListWidget->tree->header()->viewport()->removeEventFilter(this);
2419     if (capabilities.contains(QStringLiteral("SORT"))) {
2420         m_actionSortByDate->actionGroup()->setEnabled(true);
2421     } else {
2422         m_actionSortByDate->actionGroup()->setEnabled(false);
2423         msgListWidget->tree->header()->viewport()->installEventFilter(this);
2424     }
2425 
2426     msgListWidget->setFuzzySearchSupported(capabilities.contains(QStringLiteral("SEARCH=FUZZY")));
2427 
2428     m_actionShowOnlySubscribed->setEnabled(capabilities.contains(QStringLiteral("LIST-EXTENDED")));
2429     m_actionShowOnlySubscribed->setChecked(m_actionShowOnlySubscribed->isEnabled() &&
2430                                            m_settings->value(
2431                                                Common::SettingsNames::guiMailboxListShowOnlySubscribed, false).toBool());
2432     m_actionSubscribeMailbox->setEnabled(m_actionShowOnlySubscribed->isEnabled());
2433 
2434     const QStringList supportedCapabilities = Imap::Mailbox::ThreadingMsgListModel::supportedCapabilities();
2435     Q_FOREACH(const QString &capability, capabilities) {
2436         if (supportedCapabilities.contains(capability)) {
2437             actionThreadMsgList->setEnabled(true);
2438             if (actionThreadMsgList->isChecked())
2439                 slotThreadMsgList();
2440             return;
2441         }
2442     }
2443     actionThreadMsgList->setEnabled(false);
2444 }
2445 
2446 void MainWindow::slotShowImapInfo()
2447 {
2448     QString caps;
2449     Q_FOREACH(const QString &cap, imapModel()->capabilities()) {
2450         caps += tr("<li>%1</li>\n").arg(cap);
2451     }
2452 
2453     QString idString;
2454     if (!imapModel()->serverId().isEmpty() && imapModel()->capabilities().contains(QStringLiteral("ID"))) {
2455         QMap<QByteArray,QByteArray> serverId = imapModel()->serverId();
2456 
2457 #define IMAP_ID_FIELD(Var, Name) bool has_##Var = serverId.contains(Name); \
2458     QString Var = has_##Var ? QString::fromUtf8(serverId[Name]).toHtmlEscaped() : tr("Unknown");
2459         IMAP_ID_FIELD(serverName, "name");
2460         IMAP_ID_FIELD(serverVersion, "version");
2461         IMAP_ID_FIELD(os, "os");
2462         IMAP_ID_FIELD(osVersion, "os-version");
2463         IMAP_ID_FIELD(vendor, "vendor");
2464         IMAP_ID_FIELD(supportUrl, "support-url");
2465         IMAP_ID_FIELD(address, "address");
2466         IMAP_ID_FIELD(date, "date");
2467         IMAP_ID_FIELD(command, "command");
2468         IMAP_ID_FIELD(arguments, "arguments");
2469         IMAP_ID_FIELD(environment, "environment");
2470 #undef IMAP_ID_FIELD
2471         if (has_serverName) {
2472             idString = tr("<p>");
2473             if (has_serverVersion)
2474                 idString += tr("Server: %1 %2").arg(serverName, serverVersion);
2475             else
2476                 idString += tr("Server: %1").arg(serverName);
2477 
2478             if (has_vendor) {
2479                 idString += tr(" (%1)").arg(vendor);
2480             }
2481             if (has_os) {
2482                 if (has_osVersion)
2483                     idString += tr(" on %1 %2", "%1 is the operating system of an IMAP server and %2 is its version.").arg(os, osVersion);
2484                 else
2485                     idString += tr(" on %1", "%1 is the operationg system of an IMAP server.").arg(os);
2486             }
2487             idString += tr("</p>");
2488         } else {
2489             idString = tr("<p>The IMAP server did not return usable information about itself.</p>");
2490         }
2491         QString fullId;
2492         for (QMap<QByteArray,QByteArray>::const_iterator it = serverId.constBegin(); it != serverId.constEnd(); ++it) {
2493             fullId += tr("<li>%1: %2</li>").arg(QString::fromUtf8(it.key()).toHtmlEscaped(), QString::fromUtf8(it.value()).toHtmlEscaped());
2494         }
2495         idString += tr("<ul>%1</ul>").arg(fullId);
2496     } else {
2497         idString = tr("<p>The server has not provided information about its software version.</p>");
2498     }
2499 
2500     Ui::ImapInfoDialog ui;
2501     QScopedPointer<QDialog> dialog(new QDialog(this));
2502 
2503     ui.setupUi(dialog.data());
2504     ui.version->setText(tr("%1\n"
2505                            "<p>The following capabilities are currently advertised:</p>").arg(idString));
2506     ui.capabilities->setText(tr("<ul>\n%1</ul>").arg(caps));
2507 
2508     dialog->exec();
2509 }
2510 
2511 QSize MainWindow::sizeHint() const
2512 {
2513     return QSize(1150, 980);
2514 }
2515 
2516 void MainWindow::slotUpdateWindowTitle()
2517 {
2518     QModelIndex mailbox = qobject_cast<Imap::Mailbox::MsgListModel *>(m_imapAccess->msgListModel())->currentMailbox();
2519     QString profileName = QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
2520     if (!profileName.isEmpty())
2521         profileName = QLatin1String(" [") + profileName + QLatin1Char(']');
2522     if (mailbox.isValid()) {
2523         if (mailbox.data(Imap::Mailbox::RoleUnreadMessageCount).toInt()) {
2524             setWindowTitle(tr("%1 - %n unread - Trojitá", 0, mailbox.data(Imap::Mailbox::RoleUnreadMessageCount).toInt())
2525                            .arg(mailbox.data(Imap::Mailbox::RoleShortMailboxName).toString()) + profileName);
2526         } else {
2527             setWindowTitle(tr("%1 - Trojitá").arg(mailbox.data(Imap::Mailbox::RoleShortMailboxName).toString()) + profileName);
2528         }
2529     } else {
2530         setWindowTitle(tr("Trojitá") + profileName);
2531     }
2532 }
2533 
2534 void MainWindow::slotLayoutCompact()
2535 {
2536     saveSizesAndState();
2537     if (!m_mainHSplitter) {
2538         m_mainHSplitter = new QSplitter();
2539         connect(m_mainHSplitter.data(), &QSplitter::splitterMoved, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
2540         connect(m_mainHSplitter.data(), &QSplitter::splitterMoved, this, &MainWindow::possiblyLoadMessageOnSplittersChanged);
2541     }
2542     if (!m_mainVSplitter) {
2543         m_mainVSplitter = new QSplitter();
2544         m_mainVSplitter->setOrientation(Qt::Vertical);
2545         connect(m_mainVSplitter.data(), &QSplitter::splitterMoved, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
2546         connect(m_mainVSplitter.data(), &QSplitter::splitterMoved, this, &MainWindow::possiblyLoadMessageOnSplittersChanged);
2547     }
2548 
2549     m_mainVSplitter->addWidget(msgListWidget);
2550     m_mainVSplitter->addWidget(m_messageWidget);
2551     m_mainHSplitter->addWidget(mboxTree);
2552     m_mainHSplitter->addWidget(m_mainVSplitter);
2553 
2554     mboxTree->show();
2555     msgListWidget->show();
2556     m_messageWidget->show();
2557     m_mainVSplitter->show();
2558     m_mainHSplitter->show();
2559 
2560     // The mboxTree shall not expand...
2561     m_mainHSplitter->setStretchFactor(0, 0);
2562     // ...while the msgListTree shall consume all the remaining space
2563     m_mainHSplitter->setStretchFactor(1, 1);
2564     // The CompleteMessageWidget shall not not collapse
2565     m_mainVSplitter->setCollapsible(m_mainVSplitter->indexOf(m_messageWidget), false);
2566 
2567     setCentralWidget(m_mainHSplitter);
2568 
2569     delete m_mainStack;
2570 
2571     m_layoutMode = LAYOUT_COMPACT;
2572     m_settings->setValue(Common::SettingsNames::guiMainWindowLayout, Common::SettingsNames::guiMainWindowLayoutCompact);
2573     applySizesAndState();
2574 }
2575 
2576 void MainWindow::slotLayoutWide()
2577 {
2578     saveSizesAndState();
2579     if (!m_mainHSplitter) {
2580         m_mainHSplitter = new QSplitter();
2581         connect(m_mainHSplitter.data(), &QSplitter::splitterMoved, m_delayedStateSaving, static_cast<void (QTimer::*)()>(&QTimer::start));
2582         connect(m_mainHSplitter.data(), &QSplitter::splitterMoved, this, &MainWindow::possiblyLoadMessageOnSplittersChanged);
2583     }
2584 
2585     m_mainHSplitter->addWidget(mboxTree);
2586     m_mainHSplitter->addWidget(msgListWidget);
2587     m_mainHSplitter->addWidget(m_messageWidget);
2588     msgListWidget->resize(mboxTree->size());
2589     m_messageWidget->resize(mboxTree->size());
2590     m_mainHSplitter->setStretchFactor(0, 0);
2591     m_mainHSplitter->setStretchFactor(1, 1);
2592     m_mainHSplitter->setStretchFactor(2, 1);
2593 
2594     m_mainHSplitter->setCollapsible(m_mainHSplitter->indexOf(m_messageWidget), false);
2595 
2596     mboxTree->show();
2597     msgListWidget->show();
2598     m_messageWidget->show();
2599     m_mainHSplitter->show();
2600 
2601     setCentralWidget(m_mainHSplitter);
2602 
2603     delete m_mainStack;
2604     delete m_mainVSplitter;
2605 
2606     m_layoutMode = LAYOUT_WIDE;
2607     m_settings->setValue(Common::SettingsNames::guiMainWindowLayout, Common::SettingsNames::guiMainWindowLayoutWide);
2608     applySizesAndState();
2609 }
2610 
2611 void MainWindow::slotLayoutOneAtTime()
2612 {
2613     saveSizesAndState();
2614     if (m_mainStack)
2615         return;
2616 
2617     m_mainStack = new OnePanelAtTimeWidget(this, mboxTree, msgListWidget, m_messageWidget, m_mainToolbar, m_oneAtTimeGoBack);
2618     setCentralWidget(m_mainStack);
2619 
2620     delete m_mainHSplitter;
2621     delete m_mainVSplitter;
2622 
2623     m_layoutMode = LAYOUT_ONE_AT_TIME;
2624     m_settings->setValue(Common::SettingsNames::guiMainWindowLayout, Common::SettingsNames::guiMainWindowLayoutOneAtTime);
2625     applySizesAndState();
2626 }
2627 
2628 Imap::Mailbox::Model *MainWindow::imapModel() const
2629 {
2630     return qobject_cast<Imap::Mailbox::Model *>(m_imapAccess->imapModel());
2631 }
2632 
2633 void MainWindow::desktopGeometryChanged()
2634 {
2635     m_delayedStateSaving->start();
2636 }
2637 
2638 QString MainWindow::settingsKeyForLayout(const LayoutMode layout)
2639 {
2640     switch (layout) {
2641     case LAYOUT_COMPACT:
2642         return Common::SettingsNames::guiSizesInMainWinWhenCompact;
2643     case LAYOUT_WIDE:
2644         return Common::SettingsNames::guiSizesInMainWinWhenWide;
2645     case LAYOUT_ONE_AT_TIME:
2646         return Common::SettingsNames::guiSizesInaMainWinWhenOneAtATime;
2647         break;
2648     }
2649     return QString();
2650 }
2651 
2652 void MainWindow::saveSizesAndState()
2653 {
2654     if (m_skipSavingOfUI)
2655         return;
2656 
2657     QRect geometry = qApp->desktop()->availableGeometry(this);
2658     QString key = settingsKeyForLayout(m_layoutMode);
2659     if (key.isEmpty())
2660         return;
2661 
2662     QList<QByteArray> items;
2663     items << saveGeometry();
2664     items << saveState();
2665     items << (m_mainVSplitter ? m_mainVSplitter->saveState() : QByteArray());
2666     items << (m_mainHSplitter ? m_mainHSplitter->saveState() : QByteArray());
2667     items << msgListWidget->tree->header()->saveState();
2668     items << QByteArray::number(msgListWidget->tree->header()->count());
2669     for (int i = 0; i < msgListWidget->tree->header()->count(); ++i) {
2670         items << QByteArray::number(msgListWidget->tree->header()->sectionSize(i));
2671     }
2672     // a bool cannot be pushed directly onto a QByteArray so we must convert it to a number
2673     items << QByteArray::number(menuBar()->isVisible());
2674     QByteArray buf;
2675     QDataStream stream(&buf, QIODevice::WriteOnly);
2676     stream << items.size();
2677     Q_FOREACH(const QByteArray &item, items) {
2678         stream << item;
2679     }
2680 
2681     m_settings->setValue(key.arg(QString::number(geometry.width())), buf);
2682 }
2683 
2684 void MainWindow::saveRawStateSetting(bool enabled)
2685 {
2686     m_settings->setValue(Common::SettingsNames::guiAllowRawSearch, enabled);
2687 }
2688 
2689 void MainWindow::applySizesAndState()
2690 {
2691     QRect geometry = qApp->desktop()->availableGeometry(this);
2692     QString key = settingsKeyForLayout(m_layoutMode);
2693     if (key.isEmpty())
2694         return;
2695 
2696     QByteArray buf = m_settings->value(key.arg(QString::number(geometry.width()))).toByteArray();
2697     if (buf.isEmpty())
2698         return;
2699 
2700     int size;
2701     QDataStream stream(&buf, QIODevice::ReadOnly);
2702     stream >> size;
2703     QByteArray item;
2704 
2705     // There are slots connected to various events triggered by both restoreGeometry() and restoreState() which would attempt to
2706     // save our geometries and state, which is what we must avoid while this method is executing.
2707     bool skipSaving = m_skipSavingOfUI;
2708     m_skipSavingOfUI = true;
2709 
2710     if (size-- && !stream.atEnd()) {
2711         stream >> item;
2712 
2713         // https://bugreports.qt-project.org/browse/QTBUG-30636
2714         if (windowState() & Qt::WindowMaximized) {
2715             // restoreGeometry(.) restores the wrong size for at least maximized window
2716             // However, QWidget does also not notice that the configure request for this
2717             // is ignored by many window managers (because users really don't like when windows
2718             // drop themselves out of maximization) and has a wrong QWidget::geometry() idea from
2719             // the wrong assumption the request would have been hononred.
2720             //  So we just "fix" the internal geometry immediately afterwards to prevent
2721             // mislayouting
2722             // There's atm. no flicker due to this (and because Qt compresses events)
2723             // In case it ever occurs, we can frame this in setUpdatesEnabled(false/true)
2724             QRect oldGeometry = MainWindow::geometry();
2725             restoreGeometry(item);
2726             if (windowState() & Qt::WindowMaximized)
2727                 setGeometry(oldGeometry);
2728         } else {
2729             restoreGeometry(item);
2730             if (windowState() & Qt::WindowMaximized) {
2731                 // ensure to try setting the proper geometry and have the WM constrain us
2732                 setGeometry(QGuiApplication::primaryScreen()->availableGeometry());
2733             }
2734         }
2735     }
2736 
2737     if (size-- && !stream.atEnd()) {
2738         stream >> item;
2739         restoreState(item);
2740     }
2741 
2742     if (size-- && !stream.atEnd()) {
2743         stream >> item;
2744         if (m_mainVSplitter) {
2745             m_mainVSplitter->restoreState(item);
2746         }
2747     }
2748 
2749     if (size-- && !stream.atEnd()) {
2750         stream >> item;
2751         if (m_mainHSplitter) {
2752             m_mainHSplitter->restoreState(item);
2753         }
2754     }
2755 
2756     if (size-- && !stream.atEnd()) {
2757         stream >> item;
2758         msgListWidget->tree->header()->restoreState(item);
2759         // got to manually update the state of the actions which control the visibility state
2760         msgListWidget->tree->updateActionsAfterRestoredState();
2761     }
2762 
2763     connect(msgListWidget->tree->header(), &QHeaderView::sectionCountChanged, msgListWidget->tree, &MsgListView::slotHandleNewColumns);
2764 
2765     if (size-- && !stream.atEnd()) {
2766         stream >> item;
2767         bool ok;
2768         int columns = item.toInt(&ok);
2769         if (ok) {
2770             msgListWidget->tree->header()->setStretchLastSection(false);
2771             for (int i = 0; i < columns && size-- && !stream.atEnd(); ++i) {
2772                 stream >> item;
2773                 int sectionSize = item.toInt();
2774                 QHeaderView::ResizeMode resizeMode = msgListWidget->tree->resizeModeForColumn(i);
2775                 if (sectionSize > 0 && resizeMode == QHeaderView::Interactive) {
2776                     // fun fact: user cannot resize by mouse when size <= 0
2777                     msgListWidget->tree->setColumnWidth(i, sectionSize);
2778                 } else {
2779                     msgListWidget->tree->setColumnWidth(i, msgListWidget->tree->sizeHintForColumn(i));
2780                 }
2781                 msgListWidget->tree->header()->setSectionResizeMode(i, resizeMode);
2782             }
2783         }
2784     }
2785 
2786     if (size-- && !stream.atEnd()) {
2787         stream >> item;
2788         bool ok;
2789         bool visibility = item.toInt(&ok);
2790         if (ok) {
2791             menuBar()->setVisible(visibility);
2792             showMenuBar->setChecked(visibility);
2793         }
2794     }
2795 
2796     m_skipSavingOfUI = skipSaving;
2797 }
2798 
2799 void MainWindow::resizeEvent(QResizeEvent *)
2800 {
2801     m_delayedStateSaving->start();
2802 }
2803 
2804 /** @short Make sure that the message gets loaded after the splitters have changed their position */
2805 void MainWindow::possiblyLoadMessageOnSplittersChanged()
2806 {
2807     if (m_messageWidget->isVisible() && !m_messageWidget->size().isEmpty()) {
2808         // We do not have to check whether it's a different message; the setMessage() will do this or us
2809         // and there are multiple proxy models involved anyway
2810         QModelIndex index = msgListWidget->tree->currentIndex();
2811         if (index.isValid()) {
2812             // OTOH, setting an invalid QModelIndex would happily assert-fail
2813             m_messageWidget->messageView->setMessage(msgListWidget->tree->currentIndex());
2814         }
2815     }
2816 }
2817 
2818 Imap::ImapAccess *MainWindow::imapAccess() const
2819 {
2820     return m_imapAccess;
2821 }
2822 
2823 void MainWindow::enableLoggingToDisk()
2824 {
2825     protocolLogger->slotSetPersistentLogging(true);
2826 }
2827 
2828 void MainWindow::slotPluginsChanged()
2829 {
2830     Plugins::AddressbookPlugin *addressbook = pluginManager()->addressbook();
2831     if (!addressbook || !(addressbook->features() & Plugins::AddressbookPlugin::FeatureAddressbookWindow))
2832         m_actionContactEditor->setEnabled(false);
2833     else
2834         m_actionContactEditor->setEnabled(true);
2835 }
2836 
2837 /** @short Update the default action to make sure that we show a correct status of the network connection */
2838 void MainWindow::updateNetworkIndication()
2839 {
2840     if (QAction *action = qobject_cast<QAction*>(sender())) {
2841         if (action->isChecked()) {
2842             m_netToolbarDefaultAction->setIcon(action->icon());
2843         }
2844     }
2845 }
2846 
2847 void MainWindow::showStatusMessage(const QString &message)
2848 {
2849     networkIndicator->setToolTip(message);
2850     if (isActiveWindow())
2851         QToolTip::showText(networkIndicator->mapToGlobal(QPoint(0, 0)), message);
2852 }
2853 
2854 void MainWindow::slotMessageModelChanged(QAbstractItemModel *model)
2855 {
2856     mailMimeTree->setModel(model);
2857 }
2858 
2859 void MainWindow::slotFavoriteTagsChanged()
2860 {
2861     for (int i = 1; i <= m_favoriteTags->rowCount(); ++i) {
2862         QAction *action = ShortcutHandler::instance()->action(QStringLiteral("action_tag_") + QString::number(i));
2863         if (action)
2864             action->setText(tr("Tag with \"%1\"").arg(m_favoriteTags->tagNameByIndex(i - 1)));
2865     }
2866 }
2867 
2868 void MainWindow::registerComposeWindow(ComposeWidget* widget)
2869 {
2870     connect(widget, &ComposeWidget::logged, this, [this](const Common::LogKind kind, const QString& source, const QString& message) {
2871         protocolLogger->log(0, Common::LogMessage(QDateTime::currentDateTime(), kind, source, message, 0));
2872     });
2873 }
2874 
2875 }