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 }