1 /* Copyright (C) 2006 - 2015 Jan Kundrát <jkt@flaska.net>
2 Copyright (C) 2013 - 2015 Pali Rohár <pali.rohar@gmail.com>
4 This file is part of the Trojita Qt IMAP e-mail client,
5 http://trojita.flaska.net/
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of
10 the License or (at your option) version 3 or any later version
11 accepted by the membership of KDE e.V. (or its successor approved
12 by the membership of KDE e.V.), which shall act as a proxy
13 defined in Section 14 of version 3 of the license.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include <QAuthenticator>
25 #include <QDesktopServices>
26 #include <QDesktopWidget>
28 #include <QDockWidget>
29 #include <QFileDialog>
30 #include <QHeaderView>
31 #include <QItemSelectionModel>
34 #include <QMessageBox>
35 #include <QProgressBar>
36 #include <QRegularExpression>
41 #include <QStackedWidget>
43 #include <QTextDocument>
45 #include <QToolButton>
48 #include <QWheelEvent>
50 #include "configure.cmake.h"
51 #include "Common/Application.h"
52 #include "Common/InvokeMethod.h"
53 #include "Common/Paths.h"
54 #include "Common/PortNumbers.h"
55 #include "Common/SettingsNames.h"
56 #include "Composer/Mailto.h"
57 #include "Composer/SenderIdentitiesModel.h"
58 #ifdef TROJITA_HAVE_CRYPTO_MESSAGES
59 # ifdef TROJITA_HAVE_GPGMEPP
60 # include "Cryptography/GpgMe++.h"
63 #include "Imap/Model/ImapAccess.h"
64 #include "Imap/Model/MailboxTree.h"
65 #include "Imap/Model/Model.h"
66 #include "Imap/Model/ModelWatcher.h"
67 #include "Imap/Model/MsgListModel.h"
68 #include "Imap/Model/NetworkWatcher.h"
69 #include "Imap/Model/PrettyMailboxModel.h"
70 #include "Imap/Model/PrettyMsgListModel.h"
71 #include "Imap/Model/SpecialFlagNames.h"
72 #include "Imap/Model/ThreadingMsgListModel.h"
73 #include "Imap/Model/FavoriteTagsModel.h"
74 #include "Imap/Model/Utils.h"
75 #include "Imap/Tasks/ImapTask.h"
76 #include "Imap/Network/FileDownloadManager.h"
77 #include "MSA/ImapSubmit.h"
78 #include "MSA/Sendmail.h"
80 #include "Plugins/AddressbookPlugin.h"
81 #include "Plugins/PasswordPlugin.h"
82 #include "Plugins/PluginManager.h"
83 #include "CompleteMessageWidget.h"
84 #include "ComposeWidget.h"
85 #include "MailBoxTreeView.h"
86 #include "MessageListWidget.h"
87 #include "MessageView.h"
88 #include "MessageSourceWidget.h"
89 #include "Gui/MessageHeadersWidget.h"
90 #include "MsgListView.h"
91 #include "OnePanelAtTimeWidget.h"
92 #include "PasswordDialog.h"
93 #include "ProtocolLoggerWidget.h"
94 #include "SettingsDialog.h"
95 #include "SimplePartWidget.h"
96 #include "Streams/SocketFactory.h"
97 #include "TaskProgressIndicator.h"
100 #include "ShortcutHandler/ShortcutHandler.h"
102 #include "ui_CreateMailboxDialog.h"
103 #include "ui_AboutDialog.h"
105 #include "Imap/Model/ModelTest/modeltest.h"
106 #include "UiUtils/IconLoader.h"
107 #include "UiUtils/QaimDfsIterator.h"
109 /** @short All user-facing widgets and related classes */
113 static const char * const netErrorUnseen
= "net_error_unseen";
115 MainWindow::MainWindow(QSettings
*settings
): QMainWindow(), m_imapAccess(0), m_mainHSplitter(0), m_mainVSplitter(0),
116 m_mainStack(0), m_layoutMode(LAYOUT_COMPACT
), m_skipSavingOfUI(true), m_delayedStateSaving(0), m_actionSortNone(0),
117 m_ignoreStoredPassword(false), m_settings(settings
), m_pluginManager(0), m_networkErrorMessageBox(0), m_trayIcon(0)
119 setAttribute(Qt::WA_AlwaysShowToolTips
);
120 // m_pluginManager must be created before calling createWidgets
121 m_pluginManager
= new Plugins::PluginManager(this, m_settings
,
122 Common::SettingsNames::addressbookPlugin
, Common::SettingsNames::passwordPlugin
,
123 Common::SettingsNames::spellcheckerPlugin
);
124 connect(m_pluginManager
, &Plugins::PluginManager::pluginsChanged
, this, &MainWindow::slotPluginsChanged
);
125 connect(m_pluginManager
, &Plugins::PluginManager::pluginError
, this, [this](const QString
&errorMessage
) {
126 Gui::Util::messageBoxWarning(this, tr("Plugin Error"),
127 //: The %1 placeholder is a full error message as provided by Qt, ready for human consumption.
128 tr("A plugin failed to load, therefore some functionality might be lost. "
129 "You might want to update your system or report a bug to your vendor."
130 "\n\n%1").arg(errorMessage
));
132 #ifdef TROJITA_HAVE_CRYPTO_MESSAGES
133 Plugins::PluginManager::MimePartReplacers replacers
;
134 #ifdef TROJITA_HAVE_GPGMEPP
135 replacers
.emplace_back(std::make_shared
<Cryptography::GpgMeReplacer
>());
137 m_pluginManager
->setMimePartReplacers(replacers
);
140 // ImapAccess contains a wrapper for retrieving passwords through some plugin.
141 // That PasswordWatcher is used by the SettingsDialog's widgets *and* by this class,
142 // which means that ImapAccess has to be constructed before we go and open the settings dialog.
144 // FIXME: use another account-id at some point in future
145 // we are now using the profile to avoid overwriting passwords of
146 // other profiles in secure storage
147 QString profileName
= QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
148 m_imapAccess
= new Imap::ImapAccess(this, m_settings
, m_pluginManager
, profileName
);
149 connect(m_imapAccess
, &Imap::ImapAccess::cacheError
, this, &MainWindow::cacheError
);
150 connect(m_imapAccess
, &Imap::ImapAccess::checkSslPolicy
, this, &MainWindow::checkSslPolicy
, Qt::QueuedConnection
);
152 ShortcutHandler
*shortcutHandler
= new ShortcutHandler(this);
153 shortcutHandler
->setSettingsObject(m_settings
);
155 shortcutHandler
->readSettings(); // must happen after defineActions()
157 // must be created before calling createWidgets
158 m_favoriteTags
= new Imap::Mailbox::FavoriteTagsModel(this);
159 m_favoriteTags
->loadFromSettings(*m_settings
);
163 Imap::migrateSettings(m_settings
);
165 m_senderIdentities
= new Composer::SenderIdentitiesModel(this);
166 m_senderIdentities
->loadFromSettings(*m_settings
);
168 if (! m_settings
->contains(Common::SettingsNames::imapMethodKey
)) {
169 QTimer::singleShot(0, this, SLOT(slotShowSettings()));
177 slotPluginsChanged();
179 slotFavoriteTagsChanged();
180 connect(m_favoriteTags
, &QAbstractItemModel::modelReset
, this, &MainWindow::slotFavoriteTagsChanged
);
181 connect(m_favoriteTags
, &QAbstractItemModel::layoutChanged
, this, &MainWindow::slotFavoriteTagsChanged
);
182 connect(m_favoriteTags
, &QAbstractItemModel::rowsMoved
, this, &MainWindow::slotFavoriteTagsChanged
);
183 connect(m_favoriteTags
, &QAbstractItemModel::rowsInserted
, this, &MainWindow::slotFavoriteTagsChanged
);
184 connect(m_favoriteTags
, &QAbstractItemModel::rowsRemoved
, this, &MainWindow::slotFavoriteTagsChanged
);
185 connect(m_favoriteTags
, &QAbstractItemModel::dataChanged
, this, &MainWindow::slotFavoriteTagsChanged
);
187 // Please note that Qt 4.6.1 really requires passing the method signature this way, *not* using the SLOT() macro
188 QDesktopServices::setUrlHandler(QStringLiteral("mailto"), this, "slotComposeMailUrl");
189 QDesktopServices::setUrlHandler(QStringLiteral("x-trojita-manage-contact"), this, "slotManageContact");
191 slotUpdateWindowTitle();
193 CALL_LATER_NOARG(this, recoverDrafts
);
195 if (m_actionLayoutWide
->isEnabled() &&
196 m_settings
->value(Common::SettingsNames::guiMainWindowLayout
) == Common::SettingsNames::guiMainWindowLayoutWide
) {
197 m_actionLayoutWide
->trigger();
198 } else if (m_settings
->value(Common::SettingsNames::guiMainWindowLayout
) == Common::SettingsNames::guiMainWindowLayoutOneAtTime
) {
199 m_actionLayoutOneAtTime
->trigger();
201 m_actionLayoutCompact
->trigger();
204 connect(qApp
, &QGuiApplication::applicationStateChanged
, this,
205 [&](Qt::ApplicationState state
) {
206 if (state
== Qt::ApplicationActive
&& m_networkErrorMessageBox
&& m_networkErrorMessageBox
->property(netErrorUnseen
).toBool()) {
207 m_networkErrorMessageBox
->setProperty(netErrorUnseen
, false);
208 m_networkErrorMessageBox
->show();
212 // Don't listen to QDesktopWidget::resized; that is emitted too early (when it gets fired, the screen size has changed, but
213 // the workspace area is still the old one). Instead, listen to workAreaResized which gets emitted at an appropriate time.
214 // The delay is still there to guarantee some smoothing; on jkt's box there are typically three events in a rapid sequence
215 // (some of them most likely due to the fact that at first, the actual desktop gets resized, the plasma panel reacts
216 // to that and only after the panel gets resized, the available size of "the rest" is correct again).
217 // Which is why it makes sense to introduce some delay in there. The 0.5s delay is my best guess and "should work" (especially
218 // because every change bumps the timer anyway, as Thomas pointed out).
219 QTimer
*delayedResize
= new QTimer(this);
220 delayedResize
->setSingleShot(true);
221 delayedResize
->setInterval(500);
222 connect(delayedResize
, &QTimer::timeout
, this, &MainWindow::desktopGeometryChanged
);
223 connect(qApp
->desktop(), &QDesktopWidget::workAreaResized
, delayedResize
, static_cast<void (QTimer::*)()>(&QTimer::start
));
224 m_skipSavingOfUI
= false;
227 void MainWindow::defineActions()
229 ShortcutHandler
*shortcutHandler
= ShortcutHandler::instance();
230 shortcutHandler
->defineAction(QStringLiteral("action_application_exit"), QStringLiteral("application-exit"), tr("E&xit"), QKeySequence::Quit
);
231 shortcutHandler
->defineAction(QStringLiteral("action_compose_mail"), QStringLiteral("document-edit"), tr("&New Message..."), QKeySequence::New
);
232 shortcutHandler
->defineAction(QStringLiteral("action_compose_draft"), QStringLiteral("document-open-recent"), tr("&Edit Draft..."));
233 shortcutHandler
->defineAction(QStringLiteral("action_show_menubar"), QStringLiteral("view-list-text"), tr("Show Main Menu &Bar"), tr("Ctrl+M"));
234 shortcutHandler
->defineAction(QStringLiteral("action_expunge"), QStringLiteral("trash-empty"), tr("Exp&unge"), tr("Ctrl+E"));
235 shortcutHandler
->defineAction(QStringLiteral("action_mark_as_read"), QStringLiteral("mail-mark-read"), tr("Mark as &Read"), QStringLiteral("M"));
236 shortcutHandler
->defineAction(QStringLiteral("action_go_to_next_unread"), QStringLiteral("arrow-right"), tr("&Next Unread Message"), QStringLiteral("N"));
237 shortcutHandler
->defineAction(QStringLiteral("action_go_to_previous_unread"), QStringLiteral("arrow-left"), tr("&Previous Unread Message"), QStringLiteral("P"));
238 shortcutHandler
->defineAction(QStringLiteral("action_mark_as_deleted"), QStringLiteral("list-remove"), tr("Mark as &Deleted"), QKeySequence(Qt::Key_Delete
).toString());
239 shortcutHandler
->defineAction(QStringLiteral("action_mark_as_flagged"), QStringLiteral("mail-flagged"), tr("Mark as &Flagged"), QStringLiteral("S"));
240 shortcutHandler
->defineAction(QStringLiteral("action_mark_as_junk"), QStringLiteral("mail-mark-junk"), tr("Mark as &Junk"), QStringLiteral("J"));
241 shortcutHandler
->defineAction(QStringLiteral("action_mark_as_notjunk"), QStringLiteral("mail-mark-notjunk"), tr("Mark as Not &junk"), QStringLiteral("Shift+J"));
242 shortcutHandler
->defineAction(QStringLiteral("action_save_message_as"), QStringLiteral("document-save"), tr("&Save Message..."));
243 shortcutHandler
->defineAction(QStringLiteral("action_view_message_source"), QString(), tr("View Message &Source..."));
244 shortcutHandler
->defineAction(QStringLiteral("action_view_message_headers"), QString(), tr("View Message &Headers..."), tr("Ctrl+U"));
245 shortcutHandler
->defineAction(QStringLiteral("action_reply_private"), QStringLiteral("mail-reply-sender"), tr("&Private Reply"), tr("Ctrl+Shift+A"));
246 shortcutHandler
->defineAction(QStringLiteral("action_reply_all_but_me"), QStringLiteral("mail-reply-all"), tr("Reply to All &but Me"), tr("Ctrl+Shift+R"));
247 shortcutHandler
->defineAction(QStringLiteral("action_reply_all"), QStringLiteral("mail-reply-all"), tr("Reply to &All"), tr("Ctrl+Alt+Shift+R"));
248 shortcutHandler
->defineAction(QStringLiteral("action_reply_list"), QStringLiteral("mail-reply-list"), tr("Reply to &Mailing List"), tr("Ctrl+L"));
249 shortcutHandler
->defineAction(QStringLiteral("action_reply_guess"), QString(), tr("Reply by &Guess"), tr("Ctrl+R"));
250 shortcutHandler
->defineAction(QStringLiteral("action_forward_attachment"), QStringLiteral("mail-forward"), tr("&Forward"), tr("Ctrl+Shift+F"));
251 shortcutHandler
->defineAction(QStringLiteral("action_resend"), QStringLiteral("mail-resend"), tr("Resend..."));
252 shortcutHandler
->defineAction(QStringLiteral("action_archive"), QStringLiteral("mail-move-to-archive"), tr("&Archive"), QStringLiteral("A"));
253 shortcutHandler
->defineAction(QStringLiteral("action_contact_editor"), QStringLiteral("contact-unknown"), tr("Address Book..."));
254 shortcutHandler
->defineAction(QStringLiteral("action_network_offline"), QStringLiteral("network-disconnect"), tr("&Offline"));
255 shortcutHandler
->defineAction(QStringLiteral("action_network_expensive"), QStringLiteral("network-wireless"), tr("&Expensive Connection"));
256 shortcutHandler
->defineAction(QStringLiteral("action_network_online"), QStringLiteral("network-connect"), tr("&Free Access"));
257 shortcutHandler
->defineAction(QStringLiteral("action_messagewindow_close"), QStringLiteral("window-close"), tr("Close Standalone Message Window"));
258 shortcutHandler
->defineAction(QStringLiteral("action_open_messagewindow"), QString(), tr("Open message in New Window..."), QStringLiteral("Ctrl+Return"));
259 shortcutHandler
->defineAction(QStringLiteral("action_oneattime_go_back"), QStringLiteral("go-previous"), tr("Navigate Back"), QKeySequence(QKeySequence::Back
).toString());
260 shortcutHandler
->defineAction(QStringLiteral("action_zoom_in"), QStringLiteral("zoom-in"), tr("Zoom In"), QKeySequence::ZoomIn
);
261 shortcutHandler
->defineAction(QStringLiteral("action_zoom_out"), QStringLiteral("zoom-out"), tr("Zoom Out"), QKeySequence::ZoomOut
);
262 shortcutHandler
->defineAction(QStringLiteral("action_zoom_original"), QStringLiteral("zoom-original"), tr("Original Size"));
263 shortcutHandler
->defineAction(QStringLiteral("action_focus_mailbox_tree"), QString(), tr("Move Focus to Mailbox List"));
264 shortcutHandler
->defineAction(QStringLiteral("action_focus_msg_list"), QString(), tr("Move Focus to Message List"));
265 shortcutHandler
->defineAction(QStringLiteral("action_focus_quick_search"), QString(), tr("Move Focus to Quick Search"), QStringLiteral("/"));
266 shortcutHandler
->defineAction(QStringLiteral("action_tag_1"), QStringLiteral("mail-tag-1"), tr("Tag with &1st tag"), QStringLiteral("1"));
267 shortcutHandler
->defineAction(QStringLiteral("action_tag_2"), QStringLiteral("mail-tag-2"), tr("Tag with &2nd tag"), QStringLiteral("2"));
268 shortcutHandler
->defineAction(QStringLiteral("action_tag_3"), QStringLiteral("mail-tag-3"), tr("Tag with &3rd tag"), QStringLiteral("3"));
269 shortcutHandler
->defineAction(QStringLiteral("action_tag_4"), QStringLiteral("mail-tag-4"), tr("Tag with &4th tag"), QStringLiteral("4"));
270 shortcutHandler
->defineAction(QStringLiteral("action_tag_5"), QStringLiteral("mail-tag-5"), tr("Tag with &5th tag"), QStringLiteral("5"));
271 shortcutHandler
->defineAction(QStringLiteral("action_tag_6"), QStringLiteral("mail-tag-6"), tr("Tag with &6th tag"), QStringLiteral("6"));
272 shortcutHandler
->defineAction(QStringLiteral("action_tag_7"), QStringLiteral("mail-tag-7"), tr("Tag with &7th tag"), QStringLiteral("7"));
273 shortcutHandler
->defineAction(QStringLiteral("action_tag_8"), QStringLiteral("mail-tag-8"), tr("Tag with &8th tag"), QStringLiteral("8"));
274 shortcutHandler
->defineAction(QStringLiteral("action_tag_9"), QStringLiteral("mail-tag-9"), tr("Tag with &9th tag"), QStringLiteral("9"));
277 void MainWindow::createActions()
279 // The shortcuts are a little bit complicated, unfortunately. This is what the other applications use by default:
284 // list: Ctrl+Shift+L
286 // (no shortcuts for type of forwarding)
288 // new message: Ctrl+N
295 // forward as attachment: F
296 // forward inline: Shift+F
300 m_actionContactEditor
= ShortcutHandler::instance()->createAction(QStringLiteral("action_contact_editor"), this, SLOT(invokeContactEditor()), this);
302 m_mainToolbar
= addToolBar(tr("Navigation"));
303 m_mainToolbar
->setObjectName(QStringLiteral("mainToolbar"));
305 reloadMboxList
= new QAction(style()->standardIcon(QStyle::SP_ArrowRight
), tr("&Update List of Child Mailboxes"), this);
306 connect(reloadMboxList
, &QAction::triggered
, this, &MainWindow::slotReloadMboxList
);
308 resyncMbox
= new QAction(UiUtils::loadIcon(QStringLiteral("view-refresh")), tr("Check for &New Messages"), this);
309 connect(resyncMbox
, &QAction::triggered
, this, &MainWindow::slotResyncMbox
);
311 reloadAllMailboxes
= new QAction(tr("&Reload Everything"), this);
314 exitAction
= ShortcutHandler::instance()->createAction(QStringLiteral("action_application_exit"), qApp
, SLOT(quit()), this);
315 exitAction
->setStatusTip(tr("Exit the application"));
317 netOffline
= ShortcutHandler::instance()->createAction(QStringLiteral("action_network_offline"));
318 netOffline
->setCheckable(true);
320 netExpensive
= ShortcutHandler::instance()->createAction(QStringLiteral("action_network_expensive"));
321 netExpensive
->setCheckable(true);
323 netOnline
= ShortcutHandler::instance()->createAction(QStringLiteral("action_network_online"));
324 netOnline
->setCheckable(true);
327 QActionGroup
*netPolicyGroup
= new QActionGroup(this);
328 netPolicyGroup
->setExclusive(true);
329 netPolicyGroup
->addAction(netOffline
);
330 netPolicyGroup
->addAction(netExpensive
);
331 netPolicyGroup
->addAction(netOnline
);
333 //: a debugging tool showing the full contents of the whole IMAP server; all folders, messages and their parts
334 showFullView
= new QAction(UiUtils::loadIcon(QStringLiteral("edit-find-mail")), tr("Show Full &Tree Window"), this);
335 showFullView
->setCheckable(true);
336 connect(showFullView
, &QAction::triggered
, allDock
, &QWidget::setVisible
);
337 connect(allDock
, &QDockWidget::visibilityChanged
, showFullView
, &QAction::setChecked
);
339 //: list of active "tasks", entities which are performing certain action like downloading a message or syncing a mailbox
340 showTaskView
= new QAction(tr("Show ImapTask t&ree"), this);
341 showTaskView
->setCheckable(true);
342 connect(showTaskView
, &QAction::triggered
, taskDock
, &QWidget::setVisible
);
343 connect(taskDock
, &QDockWidget::visibilityChanged
, showTaskView
, &QAction::setChecked
);
345 //: a debugging tool showing the mime tree of the current message
346 showMimeView
= new QAction(tr("Show &MIME tree"), this);
347 showMimeView
->setCheckable(true);
348 connect(showMimeView
, &QAction::triggered
, mailMimeDock
, &QWidget::setVisible
);
349 connect(mailMimeDock
, &QDockWidget::visibilityChanged
, showMimeView
, &QAction::setChecked
);
351 showProtocolLogger
= new QAction(tr("Show protocol &log"), this);
352 showProtocolLogger
->setCheckable(true);
353 connect(showProtocolLogger
, &QAction::toggled
, protocolLoggerDock
, &QWidget::setVisible
);
354 connect(protocolLoggerDock
, &QDockWidget::visibilityChanged
, showProtocolLogger
, &QAction::setChecked
);
356 //: file to save the debug log into
357 logPersistent
= new QAction(tr("Log &into %1").arg(Imap::Mailbox::persistentLogFileName()), this);
358 logPersistent
->setCheckable(true);
359 connect(logPersistent
, &QAction::triggered
, protocolLogger
, &ProtocolLoggerWidget::slotSetPersistentLogging
);
360 connect(protocolLogger
, &ProtocolLoggerWidget::persistentLoggingChanged
, logPersistent
, &QAction::setChecked
);
362 showImapCapabilities
= new QAction(tr("IMAP Server In&formation..."), this);
363 connect(showImapCapabilities
, &QAction::triggered
, this, &MainWindow::slotShowImapInfo
);
365 showMenuBar
= ShortcutHandler::instance()->createAction(QStringLiteral("action_show_menubar"), this);
366 showMenuBar
->setCheckable(true);
367 showMenuBar
->setChecked(true);
368 connect(showMenuBar
, &QAction::triggered
, menuBar(), &QMenuBar::setVisible
);
369 connect(showMenuBar
, &QAction::triggered
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
371 showToolBar
= new QAction(tr("Show &Toolbar"), this);
372 showToolBar
->setCheckable(true);
373 connect(showToolBar
, &QAction::triggered
, m_mainToolbar
, &QWidget::setVisible
);
374 connect(m_mainToolbar
, &QToolBar::visibilityChanged
, showToolBar
, &QAction::setChecked
);
375 connect(m_mainToolbar
, &QToolBar::visibilityChanged
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
377 configSettings
= new QAction(UiUtils::loadIcon(QStringLiteral("configure")), tr("&Settings..."), this);
378 connect(configSettings
, &QAction::triggered
, this, &MainWindow::slotShowSettings
);
380 QAction
*triggerSearch
= new QAction(this);
381 addAction(triggerSearch
);
382 triggerSearch
->setShortcut(QKeySequence(QStringLiteral(":, =")));
383 connect(triggerSearch
, &QAction::triggered
, msgListWidget
, &MessageListWidget::focusRawSearch
);
385 addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_focus_quick_search"),
386 msgListWidget
, SLOT(focusSearch()), this));
388 addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_focus_mailbox_tree"), mboxTree
,
389 SLOT(setFocus()), this));
390 addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_focus_msg_list"), msgListWidget
->tree
,
391 SLOT(setFocus()), this));
393 m_oneAtTimeGoBack
= ShortcutHandler::instance()->createAction(QStringLiteral("action_oneattime_go_back"), this);
394 m_oneAtTimeGoBack
->setEnabled(false);
396 composeMail
= ShortcutHandler::instance()->createAction(QStringLiteral("action_compose_mail"), this, SLOT(slotComposeMail()), this);
397 m_editDraft
= ShortcutHandler::instance()->createAction(QStringLiteral("action_compose_draft"), this, SLOT(slotEditDraft()), this);
399 expunge
= ShortcutHandler::instance()->createAction(QStringLiteral("action_expunge"), this, SLOT(slotExpunge()), this);
401 m_forwardAsAttachment
= ShortcutHandler::instance()->createAction(QStringLiteral("action_forward_attachment"), this, SLOT(slotForwardAsAttachment()), this);
402 m_resend
= ShortcutHandler::instance()->createAction(QStringLiteral("action_resend"), this, SLOT(slotResend()), this);
403 markAsRead
= ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_read"), this);
404 markAsRead
->setCheckable(true);
405 msgListWidget
->tree
->addAction(markAsRead
);
406 connect(markAsRead
, &QAction::triggered
, this, &MainWindow::handleMarkAsRead
);
408 m_nextMessage
= ShortcutHandler::instance()->createAction(QStringLiteral("action_go_to_next_unread"), this, SLOT(slotNextUnread()), this);
409 msgListWidget
->tree
->addAction(m_nextMessage
);
410 m_messageWidget
->messageView
->addAction(m_nextMessage
);
412 m_previousMessage
= ShortcutHandler::instance()->createAction(QStringLiteral("action_go_to_previous_unread"), this, SLOT(slotPreviousUnread()), this);
413 msgListWidget
->tree
->addAction(m_previousMessage
);
414 m_messageWidget
->messageView
->addAction(m_previousMessage
);
416 markAsDeleted
= ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_deleted"), this);
417 markAsDeleted
->setCheckable(true);
418 msgListWidget
->tree
->addAction(markAsDeleted
);
419 connect(markAsDeleted
, &QAction::triggered
, this, &MainWindow::handleMarkAsDeleted
);
421 markAsFlagged
= ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_flagged"), this);
422 markAsFlagged
->setCheckable(true);
423 msgListWidget
->tree
->addAction(markAsFlagged
);
424 connect(markAsFlagged
, &QAction::triggered
, this, &MainWindow::handleMarkAsFlagged
);
426 markAsJunk
= ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_junk"), this);
427 markAsJunk
->setCheckable(true);
428 msgListWidget
->tree
->addAction(markAsJunk
);
429 connect(markAsJunk
, &QAction::triggered
, this, &MainWindow::handleMarkAsJunk
);
431 markAsNotJunk
= ShortcutHandler::instance()->createAction(QStringLiteral("action_mark_as_notjunk"), this);
432 markAsNotJunk
->setCheckable(true);
433 msgListWidget
->tree
->addAction(markAsNotJunk
);
434 connect(markAsNotJunk
, &QAction::triggered
, this, &MainWindow::handleMarkAsNotJunk
);
436 saveWholeMessage
= ShortcutHandler::instance()->createAction(QStringLiteral("action_save_message_as"), this, SLOT(slotSaveCurrentMessageBody()), this);
437 msgListWidget
->tree
->addAction(saveWholeMessage
);
439 viewMsgSource
= ShortcutHandler::instance()->createAction(QStringLiteral("action_view_message_source"), this, SLOT(slotViewMsgSource()), this);
440 msgListWidget
->tree
->addAction(viewMsgSource
);
442 viewMsgHeaders
= ShortcutHandler::instance()->createAction(QStringLiteral("action_view_message_headers"), this, SLOT(slotViewMsgHeaders()), this);
443 msgListWidget
->tree
->addAction(viewMsgHeaders
);
445 msgListWidget
->tree
->addAction(ShortcutHandler::instance()->createAction(QStringLiteral("action_open_messagewindow"), this,
446 SLOT(openCompleteMessageWidget()), this));
448 moveToArchive
= ShortcutHandler::instance()->createAction(QStringLiteral("action_archive"), this);
449 msgListWidget
->tree
->addAction(moveToArchive
);
450 connect(moveToArchive
, &QAction::triggered
, this, &MainWindow::handleMoveToArchive
);
452 auto addTagAction
= [=](int row
) {
453 QAction
*tag
= ShortcutHandler::instance()->createAction(QStringLiteral("action_tag_").append(QString::number(row
)), this);
454 tag
->setCheckable(true);
455 msgListWidget
->tree
->addAction(tag
);
456 connect(tag
, &QAction::triggered
, this, [=](const bool checked
) {
457 handleTag(checked
, row
- 1);
461 tag1
= addTagAction(1);
462 tag2
= addTagAction(2);
463 tag3
= addTagAction(3);
464 tag4
= addTagAction(4);
465 tag5
= addTagAction(5);
466 tag6
= addTagAction(6);
467 tag7
= addTagAction(7);
468 tag8
= addTagAction(8);
469 tag9
= addTagAction(9);
471 //: "mailbox" as a "folder of messages", not as a "mail account"
472 createChildMailbox
= new QAction(tr("Create &Child Mailbox..."), this);
473 connect(createChildMailbox
, &QAction::triggered
, this, &MainWindow::slotCreateMailboxBelowCurrent
);
475 //: "mailbox" as a "folder of messages", not as a "mail account"
476 createTopMailbox
= new QAction(tr("Create &New Mailbox..."), this);
477 connect(createTopMailbox
, &QAction::triggered
, this, &MainWindow::slotCreateTopMailbox
);
479 m_actionMarkMailboxAsRead
= new QAction(tr("&Mark Mailbox as Read"), this);
480 connect(m_actionMarkMailboxAsRead
, &QAction::triggered
, this, &MainWindow::slotMarkCurrentMailboxRead
);
482 //: "mailbox" as a "folder of messages", not as a "mail account"
483 deleteCurrentMailbox
= new QAction(tr("&Remove Mailbox"), this);
484 connect(deleteCurrentMailbox
, &QAction::triggered
, this, &MainWindow::slotDeleteCurrentMailbox
);
486 #ifdef XTUPLE_CONNECT
487 xtIncludeMailboxInSync
= new QAction(tr("&Synchronize with xTuple"), this);
488 xtIncludeMailboxInSync
->setCheckable(true);
489 connect(xtIncludeMailboxInSync
, SIGNAL(triggered()), this, SLOT(slotXtSyncCurrentMailbox()));
492 m_replyPrivate
= ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_private"), this, SLOT(slotReplyTo()), this);
493 m_replyPrivate
->setEnabled(false);
495 m_replyAllButMe
= ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_all_but_me"), this, SLOT(slotReplyAllButMe()), this);
496 m_replyAllButMe
->setEnabled(false);
498 m_replyAll
= ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_all"), this, SLOT(slotReplyAll()), this);
499 m_replyAll
->setEnabled(false);
501 m_replyList
= ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_list"), this, SLOT(slotReplyList()), this);
502 m_replyList
->setEnabled(false);
504 m_replyGuess
= ShortcutHandler::instance()->createAction(QStringLiteral("action_reply_guess"), this, SLOT(slotReplyGuess()), this);
505 m_replyGuess
->setEnabled(true);
507 actionThreadMsgList
= new QAction(UiUtils::loadIcon(QStringLiteral("format-justify-right")), tr("Show Messages in &Threads"), this);
508 actionThreadMsgList
->setCheckable(true);
509 // This action is enabled/disabled by model's capabilities
510 actionThreadMsgList
->setEnabled(false);
511 if (m_settings
->value(Common::SettingsNames::guiMsgListShowThreading
).toBool()) {
512 actionThreadMsgList
->setChecked(true);
513 // The actual threading will be performed only when model updates its capabilities
515 connect(actionThreadMsgList
, &QAction::triggered
, this, &MainWindow::slotThreadMsgList
);
517 QActionGroup
*sortOrderGroup
= new QActionGroup(this);
518 m_actionSortAscending
= new QAction(tr("&Ascending"), sortOrderGroup
);
519 m_actionSortAscending
->setCheckable(true);
520 m_actionSortAscending
->setChecked(true);
521 m_actionSortDescending
= new QAction(tr("&Descending"), sortOrderGroup
);
522 m_actionSortDescending
->setCheckable(true);
523 // QActionGroup has no toggle signal, but connecting descending will implicitly catch the acscending complement ;-)
524 connect(m_actionSortDescending
, &QAction::toggled
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
525 connect(m_actionSortDescending
, &QAction::toggled
, this, &MainWindow::slotScrollToCurrent
);
526 connect(sortOrderGroup
, &QActionGroup::triggered
, this, &MainWindow::slotSortingPreferenceChanged
);
528 QActionGroup
*sortColumnGroup
= new QActionGroup(this);
529 m_actionSortNone
= new QAction(tr("&No sorting"), sortColumnGroup
);
530 m_actionSortNone
->setCheckable(true);
531 m_actionSortThreading
= new QAction(tr("Sorted by &Threading"), sortColumnGroup
);
532 m_actionSortThreading
->setCheckable(true);
533 m_actionSortByArrival
= new QAction(tr("A&rrival"), sortColumnGroup
);
534 m_actionSortByArrival
->setCheckable(true);
535 m_actionSortByCc
= new QAction(tr("&Cc (Carbon Copy)"), sortColumnGroup
);
536 m_actionSortByCc
->setCheckable(true);
537 m_actionSortByDate
= new QAction(tr("Date from &Message Headers"), sortColumnGroup
);
538 m_actionSortByDate
->setCheckable(true);
539 m_actionSortByFrom
= new QAction(tr("&From Address"), sortColumnGroup
);
540 m_actionSortByFrom
->setCheckable(true);
541 m_actionSortBySize
= new QAction(tr("&Size"), sortColumnGroup
);
542 m_actionSortBySize
->setCheckable(true);
543 m_actionSortBySubject
= new QAction(tr("S&ubject"), sortColumnGroup
);
544 m_actionSortBySubject
->setCheckable(true);
545 m_actionSortByTo
= new QAction(tr("T&o Address"), sortColumnGroup
);
546 m_actionSortByTo
->setCheckable(true);
547 connect(sortColumnGroup
, &QActionGroup::triggered
, this, &MainWindow::slotSortingPreferenceChanged
);
548 slotSortingConfirmed(-1, Qt::AscendingOrder
);
550 actionHideRead
= new QAction(tr("&Hide Read Messages"), this);
551 actionHideRead
->setCheckable(true);
552 if (m_settings
->value(Common::SettingsNames::guiMsgListHideRead
).toBool()) {
553 actionHideRead
->setChecked(true);
554 prettyMsgListModel
->setHideRead(true);
556 connect(actionHideRead
, &QAction::triggered
, this, &MainWindow::slotHideRead
);
558 QActionGroup
*layoutGroup
= new QActionGroup(this);
559 m_actionLayoutCompact
= new QAction(tr("&Compact"), layoutGroup
);
560 m_actionLayoutCompact
->setCheckable(true);
561 m_actionLayoutCompact
->setChecked(true);
562 connect(m_actionLayoutCompact
, &QAction::triggered
, this, &MainWindow::slotLayoutCompact
);
563 m_actionLayoutWide
= new QAction(tr("&Wide"), layoutGroup
);
564 m_actionLayoutWide
->setCheckable(true);
565 connect(m_actionLayoutWide
, &QAction::triggered
, this, &MainWindow::slotLayoutWide
);
566 m_actionLayoutOneAtTime
= new QAction(tr("&One At Time"), layoutGroup
);
567 m_actionLayoutOneAtTime
->setCheckable(true);
568 connect(m_actionLayoutOneAtTime
, &QAction::triggered
, this, &MainWindow::slotLayoutOneAtTime
);
571 m_actionShowOnlySubscribed
= new QAction(tr("Show Only S&ubscribed Folders"), this);
572 m_actionShowOnlySubscribed
->setCheckable(true);
573 m_actionShowOnlySubscribed
->setEnabled(false);
574 connect(m_actionShowOnlySubscribed
, &QAction::toggled
, this, &MainWindow::slotShowOnlySubscribed
);
575 m_actionSubscribeMailbox
= new QAction(tr("Su&bscribed"), this);
576 m_actionSubscribeMailbox
->setCheckable(true);
577 m_actionSubscribeMailbox
->setEnabled(false);
578 connect(m_actionSubscribeMailbox
, &QAction::triggered
, this, &MainWindow::slotSubscribeCurrentMailbox
);
580 aboutTrojita
= new QAction(tr("&About Trojitá..."), this);
581 connect(aboutTrojita
, &QAction::triggered
, this, &MainWindow::slotShowAboutTrojita
);
583 donateToTrojita
= new QAction(tr("&Donate to the project"), this);
584 connect(donateToTrojita
, &QAction::triggered
, this, &MainWindow::slotDonateToTrojita
);
586 connectModelActions();
588 m_composeMenu
= new QMenu(tr("Compose Mail"), this);
589 m_composeMenu
->addAction(composeMail
);
590 m_composeMenu
->addAction(m_editDraft
);
591 m_composeButton
= new QToolButton(this);
592 m_composeButton
->setPopupMode(QToolButton::MenuButtonPopup
);
593 m_composeButton
->setMenu(m_composeMenu
);
594 m_composeButton
->setDefaultAction(composeMail
);
596 m_replyButton
= new QToolButton(this);
597 m_replyButton
->setPopupMode(QToolButton::MenuButtonPopup
);
598 m_replyMenu
= new QMenu(m_replyButton
);
599 m_replyMenu
->addAction(m_replyPrivate
);
600 m_replyMenu
->addAction(m_replyAllButMe
);
601 m_replyMenu
->addAction(m_replyAll
);
602 m_replyMenu
->addAction(m_replyList
);
603 m_replyButton
->setMenu(m_replyMenu
);
604 m_replyButton
->setDefaultAction(m_replyPrivate
);
606 m_mainToolbar
->addWidget(m_composeButton
);
607 m_mainToolbar
->addWidget(m_replyButton
);
608 m_mainToolbar
->addAction(m_forwardAsAttachment
);
609 m_mainToolbar
->addAction(expunge
);
610 m_mainToolbar
->addSeparator();
611 m_mainToolbar
->addAction(markAsRead
);
612 m_mainToolbar
->addAction(markAsDeleted
);
613 m_mainToolbar
->addAction(markAsFlagged
);
614 m_mainToolbar
->addAction(markAsJunk
);
615 m_mainToolbar
->addAction(markAsNotJunk
);
616 m_mainToolbar
->addAction(moveToArchive
);
618 // Push the status indicators all the way to the other side of the toolbar -- either to the far right, or far bottom.
619 QWidget
*toolbarSpacer
= new QWidget(m_mainToolbar
);
620 toolbarSpacer
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Expanding
);
621 m_mainToolbar
->addWidget(toolbarSpacer
);
623 m_mainToolbar
->addSeparator();
624 m_mainToolbar
->addWidget(busyParsersIndicator
);
626 networkIndicator
= new QToolButton(this);
627 // This is deliberate; we want to show this button in the same style as the other ones in the toolbar
628 networkIndicator
->setPopupMode(QToolButton::MenuButtonPopup
);
629 m_mainToolbar
->addWidget(networkIndicator
);
631 m_menuFromToolBar
= new QToolButton(this);
632 m_menuFromToolBar
->setIcon(UiUtils::loadIcon(QStringLiteral("menu_new")));
633 m_menuFromToolBar
->setText(QChar(0x205d)); // Unicode 'TRICOLON'
634 m_menuFromToolBar
->setPopupMode(QToolButton::MenuButtonPopup
);
635 connect(m_menuFromToolBar
, &QAbstractButton::clicked
, m_menuFromToolBar
, &QToolButton::showMenu
);
636 m_mainToolbar
->addWidget(m_menuFromToolBar
);
637 connect(showMenuBar
, &QAction::toggled
, [this](const bool menuBarVisible
) {
638 // https://bugreports.qt.io/browse/QTBUG-35768 , we have to work on the QAction, not QToolButton
639 m_mainToolbar
->actions().last()->setVisible(!menuBarVisible
);
642 busyParsersIndicator
->setFixedSize(m_mainToolbar
->iconSize());
645 // Custom widgets which are added into a QToolBar are by default aligned to the left, while QActions are justified.
646 // That sucks, because some of our widgets use multiple actions with an expanding arrow at right.
647 // Make sure everything is aligned to the left, so that the actual buttons are aligned properly and the extra arrows
648 // are, well, at right.
649 // I have no idea how this works on RTL layouts.
650 QLayout
*lay
= m_mainToolbar
->layout();
651 for (int i
= 0; i
< lay
->count(); ++i
) {
652 QLayoutItem
*it
= lay
->itemAt(i
);
653 if (it
->widget() == toolbarSpacer
) {
654 // Don't align this one, otherwise it won't push stuff when in horizontal direction
657 if (it
->widget() == busyParsersIndicator
) {
658 // It looks much better when centered
659 it
->setAlignment(Qt::AlignJustify
);
662 it
->setAlignment(Qt::AlignLeft
);
666 updateMessageFlags();
669 void MainWindow::connectModelActions()
671 connect(reloadAllMailboxes
, &QAction::triggered
, imapModel(), &Imap::Mailbox::Model::reloadMailboxList
);
672 connect(netOffline
, &QAction::triggered
,
673 qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher()), &Imap::Mailbox::NetworkWatcher::setNetworkOffline
);
674 connect(netExpensive
, &QAction::triggered
,
675 qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher()), &Imap::Mailbox::NetworkWatcher::setNetworkExpensive
);
676 connect(netOnline
, &QAction::triggered
,
677 qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher()), &Imap::Mailbox::NetworkWatcher::setNetworkOnline
);
678 netExpensive
->setEnabled(imapAccess()->isConfigured());
679 netOnline
->setEnabled(imapAccess()->isConfigured());
682 void MainWindow::createMenus()
684 #define ADD_ACTION(MENU, ACTION) \
685 MENU->addAction(ACTION); \
688 QMenu
*applicationMenu
= menuBar()->addMenu(tr("&Application"));
689 ADD_ACTION(applicationMenu
, m_actionContactEditor
);
690 QMenu
*netPolicyMenu
= applicationMenu
->addMenu(tr("&Network Access"));
691 ADD_ACTION(netPolicyMenu
, netOffline
);
692 ADD_ACTION(netPolicyMenu
, netExpensive
);
693 ADD_ACTION(netPolicyMenu
, netOnline
);
694 QMenu
*debugMenu
= applicationMenu
->addMenu(tr("&Debugging"));
695 ADD_ACTION(debugMenu
, showFullView
);
696 ADD_ACTION(debugMenu
, showTaskView
);
697 ADD_ACTION(debugMenu
, showMimeView
);
698 ADD_ACTION(debugMenu
, showProtocolLogger
);
699 ADD_ACTION(debugMenu
, logPersistent
);
700 debugMenu
->addSeparator();
701 ADD_ACTION(debugMenu
, showImapCapabilities
);
702 debugMenu
->addSeparator();
703 ADD_ACTION(debugMenu
, reloadAllMailboxes
);
704 ADD_ACTION(debugMenu
, resyncMbox
);
705 applicationMenu
->addSeparator();
706 ADD_ACTION(applicationMenu
, configSettings
);
707 ADD_ACTION(applicationMenu
, ShortcutHandler::instance()->shortcutConfigAction());
708 applicationMenu
->addSeparator();
709 ADD_ACTION(applicationMenu
, aboutTrojita
);
710 ADD_ACTION(applicationMenu
, donateToTrojita
);
711 applicationMenu
->addSeparator();
712 ADD_ACTION(applicationMenu
, exitAction
);
714 QMenu
*viewMenu
= menuBar()->addMenu(tr("&View"));
715 ADD_ACTION(viewMenu
, showMenuBar
);
716 ADD_ACTION(viewMenu
, showToolBar
);
717 QMenu
*layoutMenu
= viewMenu
->addMenu(tr("&Layout"));
718 ADD_ACTION(layoutMenu
, m_actionLayoutCompact
);
719 ADD_ACTION(layoutMenu
, m_actionLayoutWide
);
720 ADD_ACTION(layoutMenu
, m_actionLayoutOneAtTime
);
721 viewMenu
->addSeparator();
722 ADD_ACTION(viewMenu
, m_actionShowOnlySubscribed
);
724 QMenu
*mailboxMenu
= menuBar()->addMenu(tr("Mail&box"));
725 QMenu
*sortMenu
= mailboxMenu
->addMenu(tr("S&orting"));
726 ADD_ACTION(sortMenu
, m_actionSortNone
);
727 ADD_ACTION(sortMenu
, m_actionSortThreading
);
728 ADD_ACTION(sortMenu
, m_actionSortByArrival
);
729 ADD_ACTION(sortMenu
, m_actionSortByCc
);
730 ADD_ACTION(sortMenu
, m_actionSortByDate
);
731 ADD_ACTION(sortMenu
, m_actionSortByFrom
);
732 ADD_ACTION(sortMenu
, m_actionSortBySize
);
733 ADD_ACTION(sortMenu
, m_actionSortBySubject
);
734 ADD_ACTION(sortMenu
, m_actionSortByTo
);
735 sortMenu
->addSeparator();
736 ADD_ACTION(sortMenu
, m_actionSortAscending
);
737 ADD_ACTION(sortMenu
, m_actionSortDescending
);
738 sortMenu
->addSeparator();
739 ADD_ACTION(sortMenu
, actionThreadMsgList
);
740 ADD_ACTION(sortMenu
, actionHideRead
);
741 mailboxMenu
->addSeparator();
742 ADD_ACTION(mailboxMenu
, m_previousMessage
);
743 ADD_ACTION(mailboxMenu
, m_nextMessage
);
744 mailboxMenu
->addSeparator();
745 ADD_ACTION(mailboxMenu
, expunge
);
747 QMenu
*messageMenu
= menuBar()->addMenu(tr("&Message"));
748 ADD_ACTION(messageMenu
, composeMail
);
749 ADD_ACTION(messageMenu
, m_editDraft
);
750 messageMenu
->addSeparator();
751 ADD_ACTION(messageMenu
, m_replyGuess
);
752 ADD_ACTION(messageMenu
, m_replyPrivate
);
753 ADD_ACTION(messageMenu
, m_replyAll
);
754 ADD_ACTION(messageMenu
, m_replyAllButMe
);
755 ADD_ACTION(messageMenu
, m_replyList
);
756 messageMenu
->addSeparator();
757 ADD_ACTION(messageMenu
, m_forwardAsAttachment
);
758 ADD_ACTION(messageMenu
, m_resend
);
760 QMenu
*mainMenuBehindToolBar
= new QMenu(this);
761 m_menuFromToolBar
->setMenu(mainMenuBehindToolBar
);
762 m_menuFromToolBar
->menu()->addMenu(applicationMenu
);
763 m_menuFromToolBar
->menu()->addMenu(viewMenu
);
764 m_menuFromToolBar
->menu()->addMenu(mailboxMenu
);
765 m_menuFromToolBar
->menu()->addMenu(messageMenu
);
766 m_menuFromToolBar
->menu()->addSeparator();
767 m_menuFromToolBar
->menu()->addAction(showMenuBar
);
769 networkIndicator
->setMenu(netPolicyMenu
);
770 m_netToolbarDefaultAction
= new QAction(this);
771 networkIndicator
->setDefaultAction(m_netToolbarDefaultAction
);
772 connect(m_netToolbarDefaultAction
, &QAction::triggered
, networkIndicator
, &QToolButton::showMenu
);
773 connect(netOffline
, &QAction::toggled
, this, &MainWindow::updateNetworkIndication
);
774 connect(netExpensive
, &QAction::toggled
, this, &MainWindow::updateNetworkIndication
);
775 connect(netOnline
, &QAction::toggled
, this, &MainWindow::updateNetworkIndication
);
777 addToolBar(Qt::LeftToolBarArea
, m_mainToolbar
);
778 m_mainToolbar
->actions().last()->setVisible(true); // initial state to complement the default of the QMenuBar's visibility
784 void MainWindow::createWidgets()
786 // The state of the GUI is only saved after a certain time has passed. This is just an optimization to make sure
787 // we do not hit the disk continually when e.g. resizing some random widget.
788 m_delayedStateSaving
= new QTimer(this);
789 m_delayedStateSaving
->setInterval(1000);
790 m_delayedStateSaving
->setSingleShot(true);
791 connect(m_delayedStateSaving
, &QTimer::timeout
, this, &MainWindow::saveSizesAndState
);
793 mboxTree
= new MailBoxTreeView(nullptr, m_settings
);
794 mboxTree
->setDesiredExpansion(m_settings
->value(Common::SettingsNames::guiExpandedMailboxes
).toStringList());
795 connect(mboxTree
, &QWidget::customContextMenuRequested
, this, &MainWindow::showContextMenuMboxTree
);
796 connect(mboxTree
, &MailBoxTreeView::mailboxExpansionChanged
, this, [this](const QStringList
&mailboxNames
) {
797 m_settings
->setValue(Common::SettingsNames::guiExpandedMailboxes
, mailboxNames
);
800 msgListWidget
= new MessageListWidget(nullptr, m_favoriteTags
);
801 msgListWidget
->tree
->setContextMenuPolicy(Qt::CustomContextMenu
);
802 msgListWidget
->tree
->setAlternatingRowColors(true);
803 msgListWidget
->setRawSearchEnabled(m_settings
->value(Common::SettingsNames::guiAllowRawSearch
).toBool());
804 connect (msgListWidget
, &MessageListWidget::rawSearchSettingChanged
, this, &MainWindow::saveRawStateSetting
);
806 connect(msgListWidget
->tree
, &QWidget::customContextMenuRequested
, this, &MainWindow::showContextMenuMsgListTree
);
807 connect(msgListWidget
->tree
, &QAbstractItemView::activated
, this, &MainWindow::msgListClicked
);
808 connect(msgListWidget
->tree
, &QAbstractItemView::clicked
, this, &MainWindow::msgListClicked
);
809 connect(msgListWidget
->tree
, &QAbstractItemView::doubleClicked
, this, &MainWindow::openCompleteMessageWidget
);
810 connect(msgListWidget
, &MessageListWidget::requestingSearch
, this, &MainWindow::slotSearchRequested
);
811 connect(msgListWidget
->tree
->header(), &QHeaderView::sectionMoved
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
812 connect(msgListWidget
->tree
->header(), &QHeaderView::sectionResized
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
814 msgListWidget
->tree
->installEventFilter(this);
816 m_messageWidget
= new CompleteMessageWidget(this, m_settings
, m_pluginManager
, m_favoriteTags
);
817 connect(m_messageWidget
->messageView
, &MessageView::messageChanged
, this, &MainWindow::scrollMessageUp
);
818 connect(m_messageWidget
->messageView
, &MessageView::messageChanged
, this, &MainWindow::slotUpdateMessageActions
);
819 #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
820 connect(m_messageWidget
->messageView
, &MessageView::linkHovered
, [](const QString
&url
) {
822 QToolTip::hideText();
824 // indirection due to https://bugs.kde.org/show_bug.cgi?id=363783
825 QTimer::singleShot(250, [url
]() {
826 QToolTip::showText(QCursor::pos(), QObject::tr("Link target: %1").arg(UiUtils::Formatting::htmlEscaped(url
)));
831 connect(m_messageWidget
->messageView
, &MessageView::transferError
, this, &MainWindow::slotDownloadTransferError
);
832 // Do not try to get onto the homepage when we are on EXPENSIVE connection
833 if (m_settings
->value(Common::SettingsNames::appLoadHomepage
, QVariant(true)).toBool() &&
834 m_imapAccess
->preferredNetworkPolicy() == Imap::Mailbox::NETWORK_ONLINE
) {
835 m_messageWidget
->messageView
->setHomepageUrl(QUrl(QStringLiteral("http://welcome.trojita.flaska.net/%1").arg(Common::Application::version
)));
838 allDock
= new QDockWidget(tr("Everything"), this);
839 allDock
->setObjectName(QStringLiteral("allDock"));
840 allTree
= new QTreeView(allDock
);
842 allTree
->setUniformRowHeights(true);
843 allTree
->setHeaderHidden(true);
844 allDock
->setWidget(allTree
);
845 addDockWidget(Qt::LeftDockWidgetArea
, allDock
);
846 taskDock
= new QDockWidget(tr("IMAP Tasks"), this);
847 taskDock
->setObjectName(QStringLiteral("taskDock"));
848 taskTree
= new QTreeView(taskDock
);
850 taskTree
->setHeaderHidden(true);
851 taskDock
->setWidget(taskTree
);
852 addDockWidget(Qt::LeftDockWidgetArea
, taskDock
);
853 mailMimeDock
= new QDockWidget(tr("MIME Tree"), this);
854 mailMimeDock
->setObjectName(QStringLiteral("mailMimeDock"));
855 mailMimeTree
= new QTreeView(mailMimeDock
);
856 mailMimeDock
->hide();
857 mailMimeTree
->setUniformRowHeights(true);
858 mailMimeTree
->setHeaderHidden(true);
859 mailMimeDock
->setWidget(mailMimeTree
);
860 addDockWidget(Qt::RightDockWidgetArea
, mailMimeDock
);
861 connect(m_messageWidget
->messageView
, &MessageView::messageModelChanged
, this, &MainWindow::slotMessageModelChanged
);
863 protocolLoggerDock
= new QDockWidget(tr("Protocol log"), this);
864 protocolLoggerDock
->setObjectName(QStringLiteral("protocolLoggerDock"));
865 protocolLogger
= new ProtocolLoggerWidget(protocolLoggerDock
);
866 protocolLoggerDock
->hide();
867 protocolLoggerDock
->setWidget(protocolLogger
);
868 addDockWidget(Qt::BottomDockWidgetArea
, protocolLoggerDock
);
870 busyParsersIndicator
= new TaskProgressIndicator(this);
873 void MainWindow::setupModels()
875 m_imapAccess
->reloadConfiguration();
876 m_imapAccess
->doConnect();
878 m_messageWidget
->messageView
->setNetworkWatcher(qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher()));
880 auto realThreadingModel
= qobject_cast
<Imap::Mailbox::ThreadingMsgListModel
*>(m_imapAccess
->threadingMsgListModel());
881 Q_ASSERT(realThreadingModel
);
882 auto realMsgListModel
= qobject_cast
<Imap::Mailbox::MsgListModel
*>(m_imapAccess
->msgListModel());
883 Q_ASSERT(realMsgListModel
);
885 prettyMboxModel
= new Imap::Mailbox::PrettyMailboxModel(this, qobject_cast
<QAbstractItemModel
*>(m_imapAccess
->mailboxModel()));
886 prettyMboxModel
->setObjectName(QStringLiteral("prettyMboxModel"));
887 connect(realThreadingModel
, &Imap::Mailbox::ThreadingMsgListModel::sortingFailed
,
888 msgListWidget
, &MessageListWidget::slotSortingFailed
);
889 prettyMsgListModel
= new Imap::Mailbox::PrettyMsgListModel(this);
890 prettyMsgListModel
->setSourceModel(m_imapAccess
->threadingMsgListModel());
891 prettyMsgListModel
->setObjectName(QStringLiteral("prettyMsgListModel"));
893 connect(mboxTree
, &MailBoxTreeView::clicked
,
895 static_cast<void (Imap::Mailbox::MsgListModel::*)(const QModelIndex
&)>(&Imap::Mailbox::MsgListModel::setMailbox
));
896 connect(mboxTree
, &MailBoxTreeView::activated
,
898 static_cast<void (Imap::Mailbox::MsgListModel::*)(const QModelIndex
&)>(&Imap::Mailbox::MsgListModel::setMailbox
));
899 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::dataChanged
, this, &MainWindow::updateMessageFlags
);
900 connect(qobject_cast
<Imap::Mailbox::MsgListModel
*>(m_imapAccess
->msgListModel()), &Imap::Mailbox::MsgListModel::messagesAvailable
,
901 this, &MainWindow::slotScrollToUnseenMessage
);
902 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::rowsInserted
, msgListWidget
, &MessageListWidget::slotAutoEnableDisableSearch
);
903 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::rowsRemoved
, msgListWidget
, &MessageListWidget::slotAutoEnableDisableSearch
);
904 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::rowsRemoved
, this, &MainWindow::updateMessageFlags
);
905 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::layoutChanged
, msgListWidget
, &MessageListWidget::slotAutoEnableDisableSearch
);
906 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::layoutChanged
, this, &MainWindow::updateMessageFlags
);
907 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::modelReset
, msgListWidget
, &MessageListWidget::slotAutoEnableDisableSearch
);
908 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::modelReset
, this, &MainWindow::updateMessageFlags
);
909 connect(realMsgListModel
, &Imap::Mailbox::MsgListModel::mailboxChanged
, this, &MainWindow::slotMailboxChanged
);
911 connect(imapModel(), &Imap::Mailbox::Model::alertReceived
, this, &MainWindow::alertReceived
);
912 connect(imapModel(), &Imap::Mailbox::Model::imapError
, this, &MainWindow::imapError
);
913 connect(imapModel(), &Imap::Mailbox::Model::networkError
, this, &MainWindow::networkError
);
914 connect(imapModel(), &Imap::Mailbox::Model::authRequested
, this, &MainWindow::authenticationRequested
, Qt::QueuedConnection
);
915 connect(imapModel(), &Imap::Mailbox::Model::authAttemptFailed
, this, [this]() {
916 m_ignoreStoredPassword
= true;
919 connect(imapModel(), &Imap::Mailbox::Model::networkPolicyOffline
, this, &MainWindow::networkPolicyOffline
);
920 connect(imapModel(), &Imap::Mailbox::Model::networkPolicyExpensive
, this, &MainWindow::networkPolicyExpensive
);
921 connect(imapModel(), &Imap::Mailbox::Model::networkPolicyOnline
, this, &MainWindow::networkPolicyOnline
);
922 connect(imapModel(), &Imap::Mailbox::Model::connectionStateChanged
, this, [this](uint
, const Imap::ConnectionState state
) {
923 if (state
== Imap::CONN_STATE_AUTHENTICATED
) {
924 m_ignoreStoredPassword
= false;
927 connect(imapModel(), &Imap::Mailbox::Model::connectionStateChanged
, this, &MainWindow::showConnectionStatus
);
929 connect(imapModel(), &Imap::Mailbox::Model::mailboxDeletionFailed
, this, &MainWindow::slotMailboxDeleteFailed
);
930 connect(imapModel(), &Imap::Mailbox::Model::mailboxCreationFailed
, this, &MainWindow::slotMailboxCreateFailed
);
931 connect(imapModel(), &Imap::Mailbox::Model::mailboxSyncFailed
, this, &MainWindow::slotMailboxSyncFailed
);
933 connect(imapModel(), &Imap::Mailbox::Model::logged
, protocolLogger
, &ProtocolLoggerWidget::log
);
934 connect(imapModel(), &Imap::Mailbox::Model::connectionStateChanged
, protocolLogger
, &ProtocolLoggerWidget::onConnectionClosed
);
936 auto nw
= qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher());
938 connect(nw
, &Imap::Mailbox::NetworkWatcher::reconnectAttemptScheduled
,
939 this, [this](const int timeout
) {
940 showStatusMessage(tr("Attempting to reconnect in %n seconds..", 0, timeout
/1000));
942 connect(nw
, &Imap::Mailbox::NetworkWatcher::resetReconnectState
, this, &MainWindow::slotResetReconnectState
);
944 connect(imapModel(), &Imap::Mailbox::Model::mailboxFirstUnseenMessage
, this, &MainWindow::slotScrollToUnseenMessage
);
946 connect(imapModel(), &Imap::Mailbox::Model::capabilitiesUpdated
, this, &MainWindow::slotCapabilitiesUpdated
);
948 connect(m_imapAccess
->msgListModel(), &QAbstractItemModel::modelReset
, this, &MainWindow::slotUpdateWindowTitle
);
949 connect(imapModel(), &Imap::Mailbox::Model::messageCountPossiblyChanged
, this, &MainWindow::slotUpdateWindowTitle
);
951 connect(prettyMsgListModel
, &Imap::Mailbox::PrettyMsgListModel::sortingPreferenceChanged
, this, &MainWindow::slotSortingConfirmed
);
953 //Imap::Mailbox::ModelWatcher* w = new Imap::Mailbox::ModelWatcher( this );
954 //w->setModel( imapModel() );
956 //ModelTest* tester = new ModelTest( prettyMboxModel, this ); // when testing, test just one model at time
958 mboxTree
->setModel(prettyMboxModel
);
959 msgListWidget
->tree
->setModel(prettyMsgListModel
);
960 connect(msgListWidget
->tree
->selectionModel(), &QItemSelectionModel::selectionChanged
, this, &MainWindow::updateMessageFlags
);
962 allTree
->setModel(imapModel());
963 taskTree
->setModel(imapModel()->taskModel());
964 connect(imapModel()->taskModel(), &QAbstractItemModel::layoutChanged
, taskTree
, &QTreeView::expandAll
);
965 connect(imapModel()->taskModel(), &QAbstractItemModel::modelReset
, taskTree
, &QTreeView::expandAll
);
966 connect(imapModel()->taskModel(), &QAbstractItemModel::rowsInserted
, taskTree
, &QTreeView::expandAll
);
967 connect(imapModel()->taskModel(), &QAbstractItemModel::rowsRemoved
, taskTree
, &QTreeView::expandAll
);
968 connect(imapModel()->taskModel(), &QAbstractItemModel::rowsMoved
, taskTree
, &QTreeView::expandAll
);
970 busyParsersIndicator
->setImapModel(imapModel());
972 auto accountIconName
= m_settings
->value(Common::SettingsNames::imapAccountIcon
).toString();
973 if (accountIconName
.isEmpty()) {
974 qApp
->setWindowIcon(UiUtils::loadIcon(QStringLiteral("trojita")));
975 } else if (accountIconName
.contains(QDir::separator())) {
976 // Absolute paths are OK for users, but unsupported by our icon loader
977 qApp
->setWindowIcon(QIcon(accountIconName
));
979 qApp
->setWindowIcon(UiUtils::loadIcon(accountIconName
));
983 void MainWindow::createSysTray()
988 qApp
->setQuitOnLastWindowClosed(false);
990 m_trayIcon
= new QSystemTrayIcon(this);
991 handleTrayIconChange();
993 QAction
* quitAction
= new QAction(tr("&Quit"), m_trayIcon
);
994 connect(quitAction
, &QAction::triggered
, qApp
, &QApplication::quit
);
996 QMenu
*trayIconMenu
= new QMenu(this);
997 trayIconMenu
->addAction(quitAction
);
998 m_trayIcon
->setContextMenu(trayIconMenu
);
1000 // QMenu cannot be a child of QSystemTrayIcon, and we don't want the QMenu in MainWindow scope.
1001 connect(m_trayIcon
, &QObject::destroyed
, trayIconMenu
, &QObject::deleteLater
);
1003 connect(m_trayIcon
, &QSystemTrayIcon::activated
, this, &MainWindow::slotIconActivated
);
1004 connect(imapModel(), &Imap::Mailbox::Model::messageCountPossiblyChanged
, this, &MainWindow::handleTrayIconChange
);
1005 m_trayIcon
->setVisible(true);
1009 void MainWindow::removeSysTray()
1014 qApp
->setQuitOnLastWindowClosed(true);
1017 void MainWindow::slotToggleSysTray()
1019 bool showSystray
= m_settings
->value(Common::SettingsNames::guiShowSystray
, QVariant(true)).toBool();
1020 if (showSystray
&& !m_trayIcon
&& QSystemTrayIcon::isSystemTrayAvailable()) {
1022 } else if (!showSystray
&& m_trayIcon
) {
1027 void MainWindow::handleTrayIconChange()
1032 const bool isOffline
= qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher())->effectiveNetworkPolicy()
1033 == Imap::Mailbox::NETWORK_OFFLINE
;
1034 auto pixmap
= qApp
->windowIcon()
1035 .pixmap(QSize(32, 32), isOffline
? QIcon::Disabled
: QIcon::Normal
);
1037 auto profileName
= QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
1038 if (profileName
.isEmpty()) {
1039 tooltip
= QStringLiteral("Trojitá");
1041 tooltip
= QStringLiteral("Trojitá [%1]").arg(profileName
);
1044 int unreadCount
= 0;
1045 bool numbersValid
= false;
1047 auto watchingMode
= settings()->value(Common::SettingsNames::watchedFoldersKey
).toString();
1048 if (watchingMode
== Common::SettingsNames::watchAll
|| watchingMode
== Common::SettingsNames::watchSubscribed
) {
1049 bool subscribedOnly
= watchingMode
== Common::SettingsNames::watchSubscribed
;
1050 unreadCount
= std::accumulate(UiUtils::QaimDfsIterator(m_imapAccess
->mailboxModel()->index(0, 0), m_imapAccess
->mailboxModel()),
1051 UiUtils::QaimDfsIterator(), 0, [subscribedOnly
](const int acc
, const QModelIndex
&idx
) {
1053 if (subscribedOnly
&& !idx
.data(Imap::Mailbox::RoleMailboxIsSubscribed
).toBool())
1056 auto x
= idx
.data(Imap::Mailbox::RoleUnreadMessageCount
).toInt();
1063 // only show stuff if there are some mailboxes, and if there are such messages
1064 numbersValid
= m_imapAccess
->mailboxModel()->hasChildren() && unreadCount
> 0;
1067 // just for the INBOX
1068 QModelIndex mailbox
= imapModel()->index(1, 0, QModelIndex());
1069 if (mailbox
.isValid() && mailbox
.data(Imap::Mailbox::RoleMailboxName
).toString() == QLatin1String("INBOX")
1070 && mailbox
.data(Imap::Mailbox::RoleUnreadMessageCount
).toInt() > 0) {
1071 unreadCount
= mailbox
.data(Imap::Mailbox::RoleUnreadMessageCount
).toInt();
1072 numbersValid
= true;
1078 f
.setPixelSize(pixmap
.height() * 0.59);
1079 f
.setWeight(QFont::Bold
);
1081 QString text
= QString::number(unreadCount
);
1083 if (unreadCount
> 666) {
1084 // You just have too many messages.
1085 text
= QStringLiteral("🐮");
1086 fm
= QFontMetrics(f
);
1087 } else if (fm
.width(text
) > pixmap
.width()) {
1088 f
.setPixelSize(f
.pixelSize() * pixmap
.width() / fm
.width(text
));
1089 fm
= QFontMetrics(f
);
1092 QRect boundingRect
= fm
.tightBoundingRect(text
);
1093 boundingRect
.setWidth(boundingRect
.width() + 2);
1094 boundingRect
.setHeight(boundingRect
.height() + 2);
1095 boundingRect
.moveCenter(QPoint(pixmap
.width() / 2, pixmap
.height() / 2));
1096 boundingRect
= boundingRect
.intersected(pixmap
.rect());
1099 path
.addText(boundingRect
.bottomLeft(), f
, text
);
1101 QPainter
painter(&pixmap
);
1102 painter
.setRenderHint(QPainter::Antialiasing
);
1103 painter
.setPen(QColor(255,255,255, 180));
1104 painter
.setBrush(isOffline
? Qt::red
: Qt::black
);
1105 painter
.drawPath(path
);
1107 //: This is a tooltip for the tray icon. It will be prefixed by something like "Trojita" or "Trojita [work]"
1108 tooltip
+= tr(" - %n unread message(s)", 0, unreadCount
);
1109 } else if (isOffline
) {
1110 //: A tooltip suffix when offline. The prefix is something like "Trojita" or "Trojita [work]"
1111 tooltip
+= tr(" - offline");
1113 m_trayIcon
->setToolTip(tooltip
);
1114 m_trayIcon
->setIcon(QIcon(pixmap
));
1117 void MainWindow::closeEvent(QCloseEvent
*event
)
1119 if (m_trayIcon
&& m_trayIcon
->isVisible()) {
1120 Util::askForSomethingUnlessTold(tr("Trojitá"),
1121 tr("The application will continue in systray. This can be disabled within the settings."),
1122 Common::SettingsNames::guiOnSystrayClose
, QMessageBox::Ok
, this, m_settings
);
1128 bool MainWindow::eventFilter(QObject
*o
, QEvent
*e
)
1130 if (msgListWidget
&& o
== msgListWidget
->tree
&& m_messageWidget
->messageView
) {
1131 if (e
->type() == QEvent::KeyPress
) {
1132 QKeyEvent
*keyEvent
= static_cast<QKeyEvent
*>(e
);
1133 if (keyEvent
->key() == Qt::Key_Space
|| keyEvent
->key() == Qt::Key_Backspace
) {
1134 QCoreApplication::sendEvent(m_messageWidget
, keyEvent
);
1141 if (msgListWidget
&& msgListWidget
->tree
&& o
== msgListWidget
->tree
->header()->viewport()) {
1142 // installed if sorting is not really possible.
1143 QWidget
*header
= static_cast<QWidget
*>(o
);
1144 QMouseEvent
*mouse
= static_cast<QMouseEvent
*>(e
);
1145 if (e
->type() == QEvent::MouseButtonPress
) {
1146 if (mouse
->button() == Qt::LeftButton
&& header
->cursor().shape() == Qt::ArrowCursor
) {
1147 m_headerDragStart
= mouse
->pos();
1151 if (e
->type() == QEvent::MouseButtonRelease
) {
1152 if (mouse
->button() == Qt::LeftButton
&& header
->cursor().shape() == Qt::ArrowCursor
&&
1153 (m_headerDragStart
- mouse
->pos()).manhattanLength() < QApplication::startDragDistance()) {
1154 m_actionSortDescending
->toggle();
1155 Qt::SortOrder order
= m_actionSortDescending
->isChecked() ? Qt::DescendingOrder
: Qt::AscendingOrder
;
1156 msgListWidget
->tree
->header()->setSortIndicator(-1, order
);
1157 return true; // prevent regular click
1164 void MainWindow::slotIconActivated(const QSystemTrayIcon::ActivationReason reason
)
1166 if (reason
== QSystemTrayIcon::Trigger
) {
1167 setVisible(!isVisible());
1173 void MainWindow::showMainWindow()
1180 void MainWindow::msgListClicked(const QModelIndex
&index
)
1182 Q_ASSERT(index
.isValid());
1184 if (qApp
->keyboardModifiers() & Qt::ShiftModifier
|| qApp
->keyboardModifiers() & Qt::ControlModifier
)
1187 if (! index
.data(Imap::Mailbox::RoleMessageUid
).isValid())
1190 // Because it's quite possible that we have switched into another mailbox, make sure that we're in the "current" one so that
1191 // user will be notified about new arrivals, etc.
1192 QModelIndex translated
= Imap::deproxifiedIndex(index
);
1193 imapModel()->switchToMailbox(translated
.parent().parent());
1195 if (index
.column() == Imap::Mailbox::MsgListModel::SEEN
) {
1196 if (!translated
.data(Imap::Mailbox::RoleIsFetched
).toBool())
1198 Imap::Mailbox::FlagsOperation flagOp
= translated
.data(Imap::Mailbox::RoleMessageIsMarkedRead
).toBool() ?
1199 Imap::Mailbox::FLAG_REMOVE
: Imap::Mailbox::FLAG_ADD
;
1200 imapModel()->markMessagesRead(QModelIndexList() << translated
, flagOp
);
1202 if (translated
== m_messageWidget
->messageView
->currentMessage()) {
1203 m_messageWidget
->messageView
->stopAutoMarkAsRead();
1205 } else if (index
.column() == Imap::Mailbox::MsgListModel::FLAGGED
) {
1206 if (!translated
.data(Imap::Mailbox::RoleIsFetched
).toBool())
1209 Imap::Mailbox::FlagsOperation flagOp
= translated
.data(Imap::Mailbox::RoleMessageIsMarkedFlagged
).toBool() ?
1210 Imap::Mailbox::FLAG_REMOVE
: Imap::Mailbox::FLAG_ADD
;
1211 imapModel()->setMessageFlags(QModelIndexList() << translated
, Imap::Mailbox::FlagNames::flagged
, flagOp
);
1213 if ((m_messageWidget
->isVisible() && !m_messageWidget
->size().isEmpty()) || m_layoutMode
== LAYOUT_ONE_AT_TIME
) {
1214 // isVisible() won't work, the splitter manipulates width, not the visibility state
1215 m_messageWidget
->messageView
->setMessage(index
);
1217 msgListWidget
->tree
->setCurrentIndex(index
);
1221 void MainWindow::openCompleteMessageWidget()
1223 const QModelIndex index
= msgListWidget
->tree
->currentIndex();
1225 if (! index
.data(Imap::Mailbox::RoleMessageUid
).isValid())
1228 CompleteMessageWidget
*widget
= new CompleteMessageWidget(0, m_settings
, m_pluginManager
, m_favoriteTags
);
1229 widget
->messageView
->setMessage(index
);
1230 widget
->messageView
->setNetworkWatcher(qobject_cast
<Imap::Mailbox::NetworkWatcher
*>(m_imapAccess
->networkWatcher()));
1231 widget
->setFocusPolicy(Qt::StrongFocus
);
1232 widget
->setWindowTitle(index
.data(Imap::Mailbox::RoleMessageSubject
).toString());
1233 widget
->setAttribute(Qt::WA_DeleteOnClose
);
1234 QAction
*closeAction
= ShortcutHandler::instance()->createAction(QStringLiteral("action_messagewindow_close"), widget
, SLOT(close()), widget
);
1235 widget
->addAction(closeAction
);
1239 void MainWindow::showContextMenuMboxTree(const QPoint
&position
)
1241 QList
<QAction
*> actionList
;
1242 if (mboxTree
->indexAt(position
).isValid()) {
1243 actionList
.append(createChildMailbox
);
1244 actionList
.append(deleteCurrentMailbox
);
1245 actionList
.append(m_actionMarkMailboxAsRead
);
1246 actionList
.append(resyncMbox
);
1247 actionList
.append(reloadMboxList
);
1249 actionList
.append(m_actionSubscribeMailbox
);
1250 m_actionSubscribeMailbox
->setChecked(mboxTree
->indexAt(position
).data(Imap::Mailbox::RoleMailboxIsSubscribed
).toBool());
1252 #ifdef XTUPLE_CONNECT
1253 actionList
.append(xtIncludeMailboxInSync
);
1254 xtIncludeMailboxInSync
->setChecked(
1255 m_settings
->value(Common::SettingsNames::xtSyncMailboxList
).toStringList().contains(
1256 mboxTree
->indexAt(position
).data(Imap::Mailbox::RoleMailboxName
).toString()));
1259 actionList
.append(createTopMailbox
);
1261 actionList
.append(reloadAllMailboxes
);
1262 actionList
.append(m_actionShowOnlySubscribed
);
1263 QMenu::exec(actionList
, mboxTree
->mapToGlobal(position
), nullptr, this);
1266 void MainWindow::showContextMenuMsgListTree(const QPoint
&position
)
1268 QList
<QAction
*> actionList
;
1269 QModelIndex index
= msgListWidget
->tree
->indexAt(position
);
1270 if (index
.isValid()) {
1271 updateMessageFlagsOf(index
);
1272 actionList
.append(markAsRead
);
1273 actionList
.append(markAsDeleted
);
1274 actionList
.append(markAsFlagged
);
1275 actionList
.append(markAsJunk
);
1276 actionList
.append(markAsNotJunk
);
1277 actionList
.append(moveToArchive
);
1278 actionList
.append(m_actionMarkMailboxAsRead
);
1279 actionList
.append(saveWholeMessage
);
1280 actionList
.append(viewMsgSource
);
1281 actionList
.append(viewMsgHeaders
);
1282 auto appendTagIfExists
= [this,&actionList
](const int row
, QAction
*tag
) {
1283 if (m_favoriteTags
->rowCount() > row
- 1)
1284 actionList
.append(tag
);
1286 appendTagIfExists(1, tag1
);
1287 appendTagIfExists(2, tag2
);
1288 appendTagIfExists(3, tag3
);
1289 appendTagIfExists(4, tag4
);
1290 appendTagIfExists(5, tag5
);
1291 appendTagIfExists(6, tag6
);
1292 appendTagIfExists(7, tag7
);
1293 appendTagIfExists(8, tag8
);
1294 appendTagIfExists(9, tag9
);
1296 if (! actionList
.isEmpty())
1297 QMenu::exec(actionList
, msgListWidget
->tree
->mapToGlobal(position
), nullptr, this);
1300 /** @short Ask for an updated list of mailboxes situated below the selected one
1303 void MainWindow::slotReloadMboxList()
1305 Q_FOREACH(const QModelIndex
&item
, mboxTree
->selectionModel()->selectedIndexes()) {
1306 Q_ASSERT(item
.isValid());
1307 if (item
.column() != 0)
1309 Imap::Mailbox::TreeItemMailbox
*mbox
= dynamic_cast<Imap::Mailbox::TreeItemMailbox
*>(
1310 Imap::Mailbox::Model::realTreeItem(item
)
1313 mbox
->rescanForChildMailboxes(imapModel());
1317 /** @short Request a check for new messages in selected mailbox */
1318 void MainWindow::slotResyncMbox()
1320 if (! imapModel()->isNetworkAvailable())
1323 Q_FOREACH(const QModelIndex
&item
, mboxTree
->selectionModel()->selectedIndexes()) {
1324 Q_ASSERT(item
.isValid());
1325 if (item
.column() != 0)
1327 imapModel()->resyncMailbox(item
);
1331 void MainWindow::alertReceived(const QString
&message
)
1333 //: "ALERT" is a special warning which we're required to show to the user
1334 Gui::Util::messageBoxWarning(this, tr("IMAP Alert"), message
);
1337 void MainWindow::imapError(const QString
&message
)
1339 Gui::Util::messageBoxCritical(this, tr("IMAP Protocol Error"), message
);
1340 // Show the IMAP logger -- maybe some user will take that as a hint that they shall include it in the bug report.
1342 showProtocolLogger
->setChecked(true);
1345 void MainWindow::networkError(const QString
&message
)
1347 const QString title
= tr("Network Error");
1348 if (!m_networkErrorMessageBox
) {
1349 m_networkErrorMessageBox
= new QMessageBox(QMessageBox::Critical
, title
,
1350 QString(), QMessageBox::Ok
, this);
1352 // User must be informed about a new (but not recurring) error
1353 if (message
!= m_networkErrorMessageBox
->text()) {
1354 m_networkErrorMessageBox
->setText(message
);
1355 if (qApp
->applicationState() == Qt::ApplicationActive
) {
1356 m_networkErrorMessageBox
->setProperty(netErrorUnseen
, false);
1357 m_networkErrorMessageBox
->show();
1359 m_networkErrorMessageBox
->setProperty(netErrorUnseen
, true);
1360 if (m_trayIcon
&& m_trayIcon
->isVisible())
1361 m_trayIcon
->showMessage(title
, message
, QSystemTrayIcon::Warning
, 3333);
1366 void MainWindow::cacheError(const QString
&message
)
1368 Gui::Util::messageBoxCritical(this, tr("IMAP Cache Error"),
1369 tr("The caching subsystem managing a cache of the data already "
1370 "downloaded from the IMAP server is having troubles. "
1371 "All caching will be disabled.\n\n%1").arg(message
));
1374 void MainWindow::networkPolicyOffline()
1376 netExpensive
->setChecked(false);
1377 netOnline
->setChecked(false);
1378 netOffline
->setChecked(true);
1379 updateActionsOnlineOffline(false);
1380 showStatusMessage(tr("Offline"));
1381 handleTrayIconChange();
1384 void MainWindow::networkPolicyExpensive()
1386 netOffline
->setChecked(false);
1387 netOnline
->setChecked(false);
1388 netExpensive
->setChecked(true);
1389 updateActionsOnlineOffline(true);
1390 handleTrayIconChange();
1393 void MainWindow::networkPolicyOnline()
1395 netOffline
->setChecked(false);
1396 netExpensive
->setChecked(false);
1397 netOnline
->setChecked(true);
1398 updateActionsOnlineOffline(true);
1399 handleTrayIconChange();
1402 /** @short Deletes a network error message box instance upon resetting of reconnect state */
1403 void MainWindow::slotResetReconnectState()
1405 if (m_networkErrorMessageBox
) {
1406 delete m_networkErrorMessageBox
;
1407 m_networkErrorMessageBox
= 0;
1411 void MainWindow::slotShowSettings()
1413 SettingsDialog
*dialog
= new SettingsDialog(this, m_senderIdentities
, m_favoriteTags
, m_settings
);
1414 if (dialog
->exec() == QDialog::Accepted
) {
1415 // FIXME: wipe cache in case we're moving between servers
1418 connectModelActions();
1419 // The systray is still connected to the old model -- got to make sure it's getting updated
1421 slotToggleSysTray();
1423 QString method
= m_settings
->value(Common::SettingsNames::imapMethodKey
).toString();
1424 if (method
!= Common::SettingsNames::methodTCP
&& method
!= Common::SettingsNames::methodSSL
&&
1425 method
!= Common::SettingsNames::methodProcess
) {
1426 Gui::Util::messageBoxCritical(this, tr("No Configuration"),
1427 tr("No IMAP account is configured. Trojitá cannot do much without one."));
1429 applySizesAndState();
1432 void MainWindow::authenticationRequested()
1434 Plugins::PasswordPlugin
*password
= pluginManager()->password();
1436 // FIXME: use another account-id at some point in future
1437 // Currently the accountName will be empty unless Trojita has been
1438 // called with a profile, and then the profile will be used as the
1440 QString accountName
= m_imapAccess
->accountName();
1441 if (accountName
.isEmpty())
1442 accountName
= QStringLiteral("account-0");
1443 Plugins::PasswordJob
*job
= password
->requestPassword(accountName
, QStringLiteral("imap"));
1445 connect(job
, &Plugins::PasswordJob::passwordAvailable
, this, [this](const QString
&password
) {
1446 authenticationContinue(password
);
1448 connect(job
, &Plugins::PasswordJob::error
, this, [this](const Plugins::PasswordJob::Error error
, const QString
&message
) {
1449 if (error
== Plugins::PasswordJob::Error::NoSuchPassword
) {
1450 authenticationContinue(QString());
1452 authenticationContinue(QString(), tr("Failed to retrieve password from the store: %1").arg(message
));
1455 job
->setAutoDelete(true);
1461 authenticationContinue(QString());
1465 void MainWindow::authenticationContinue(const QString
&password
, const QString
&errorMessage
)
1467 const QString
&user
= m_settings
->value(Common::SettingsNames::imapUserKey
).toString();
1468 QString pass
= password
;
1469 if (m_ignoreStoredPassword
|| pass
.isEmpty()) {
1470 auto dialog
= PasswordDialog::getPassword(this, tr("Authentication Required"),
1471 tr("<p>Please provide IMAP password for user <b>%1</b> on <b>%2</b>:</p>").arg(
1472 user
.toHtmlEscaped(),
1473 m_settings
->value(Common::SettingsNames::imapHostKey
).toString().toHtmlEscaped()
1475 errorMessage
+ (errorMessage
.isEmpty() ? QString() : QStringLiteral("\n\n"))
1476 + imapModel()->imapAuthError());
1477 connect(dialog
, &PasswordDialog::gotPassword
, imapModel(), &Imap::Mailbox::Model::setImapPassword
);
1478 connect(dialog
, &PasswordDialog::rejected
, imapModel(), &Imap::Mailbox::Model::unsetImapPassword
);
1480 imapModel()->setImapPassword(pass
);
1484 void MainWindow::checkSslPolicy()
1486 m_imapAccess
->setSslPolicy(QMessageBox(static_cast<QMessageBox::Icon
>(m_imapAccess
->sslInfoIcon()),
1487 m_imapAccess
->sslInfoTitle(), m_imapAccess
->sslInfoMessage(),
1488 QMessageBox::Yes
| QMessageBox::No
, this).exec() == QMessageBox::Yes
);
1491 void MainWindow::nukeModels()
1493 m_messageWidget
->messageView
->setEmpty();
1494 mboxTree
->setModel(0);
1495 msgListWidget
->tree
->setModel(0);
1496 allTree
->setModel(0);
1497 taskTree
->setModel(0);
1498 delete prettyMsgListModel
;
1499 prettyMsgListModel
= 0;
1500 delete prettyMboxModel
;
1501 prettyMboxModel
= 0;
1504 void MainWindow::recoverDrafts()
1506 QDir
draftPath(Common::writablePath(Common::LOCATION_CACHE
) + QLatin1String("Drafts/"));
1507 QStringList
drafts(draftPath
.entryList(QStringList() << QStringLiteral("*.draft")));
1508 Q_FOREACH(const QString
&draft
, drafts
) {
1509 ComposeWidget
*w
= ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createDraft(this, draftPath
.filePath(draft
)), this);
1510 // No need to further try creating widgets for drafts if a nullptr is being returned by ComposeWidget::warnIfMsaNotConfigured
1516 void MainWindow::slotComposeMail()
1518 ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createBlank(this), this);
1521 void MainWindow::slotEditDraft()
1523 QString
path(Common::writablePath(Common::LOCATION_DATA
) + tr("Drafts"));
1524 QDir().mkpath(path
);
1525 path
= QFileDialog::getOpenFileName(this, tr("Edit draft"), path
, tr("Drafts") + QLatin1String(" (*.draft)"));
1526 if (!path
.isNull()) {
1527 ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createDraft(this, path
), this);
1531 QModelIndexList
MainWindow::translatedSelection() const
1533 QModelIndexList translatedIndexes
;
1534 Q_FOREACH(const QModelIndex
&index
, msgListWidget
->tree
->selectedTree()) {
1535 translatedIndexes
<< Imap::deproxifiedIndex(index
);
1537 return translatedIndexes
;
1540 void MainWindow::handleMarkAsRead(bool value
)
1542 const QModelIndexList translatedIndexes
= translatedSelection();
1543 if (translatedIndexes
.isEmpty()) {
1544 qDebug() << "Model::handleMarkAsRead: no valid messages";
1546 imapModel()->markMessagesRead(translatedIndexes
, value
? Imap::Mailbox::FLAG_ADD
: Imap::Mailbox::FLAG_REMOVE
);
1547 if (translatedIndexes
.contains(m_messageWidget
->messageView
->currentMessage())) {
1548 m_messageWidget
->messageView
->stopAutoMarkAsRead();
1553 void MainWindow::slotNextUnread()
1555 QModelIndex current
= msgListWidget
->tree
->currentIndex();
1557 UiUtils::gotoNext(msgListWidget
->tree
->model(), current
,
1558 [](const QModelIndex
&idx
) { return !idx
.data(Imap::Mailbox::RoleMessageIsMarkedRead
).toBool(); },
1559 [this](const QModelIndex
&idx
) {
1560 Q_ASSERT(!idx
.data(Imap::Mailbox::RoleMessageIsMarkedRead
).toBool());
1561 m_messageWidget
->messageView
->setMessage(idx
);
1562 msgListWidget
->tree
->setCurrentIndex(idx
);
1569 void MainWindow::slotPreviousUnread()
1571 QModelIndex current
= msgListWidget
->tree
->currentIndex();
1573 UiUtils::gotoPrevious(msgListWidget
->tree
->model(), current
,
1574 [](const QModelIndex
&idx
) { return !idx
.data(Imap::Mailbox::RoleMessageIsMarkedRead
).toBool(); },
1575 [this](const QModelIndex
&idx
) {
1576 Q_ASSERT(!idx
.data(Imap::Mailbox::RoleMessageIsMarkedRead
).toBool());
1577 m_messageWidget
->messageView
->setMessage(idx
);
1578 msgListWidget
->tree
->setCurrentIndex(idx
);
1585 void MainWindow::handleTag(const bool checked
, const int index
)
1587 const QModelIndexList
&translatedIndexes
= translatedSelection();
1588 if (translatedIndexes
.isEmpty()) {
1589 qDebug() << "Model::handleTag: no valid messages";
1591 const auto &tagName
= m_favoriteTags
->tagNameByIndex(index
);
1592 if (!tagName
.isEmpty())
1593 imapModel()->setMessageFlags(translatedIndexes
, tagName
, checked
? Imap::Mailbox::FLAG_ADD
: Imap::Mailbox::FLAG_REMOVE
);
1597 void MainWindow::handleMarkAsDeleted(bool value
)
1599 const QModelIndexList translatedIndexes
= translatedSelection();
1600 if (translatedIndexes
.isEmpty()) {
1601 qDebug() << "Model::handleMarkAsDeleted: no valid messages";
1603 imapModel()->markMessagesDeleted(translatedIndexes
, value
? Imap::Mailbox::FLAG_ADD
: Imap::Mailbox::FLAG_REMOVE
);
1607 void MainWindow::handleMarkAsFlagged(const bool value
)
1609 const QModelIndexList translatedIndexes
= translatedSelection();
1610 if (translatedIndexes
.isEmpty()) {
1611 qDebug() << "Model::handleMarkAsFlagged: no valid messages";
1613 imapModel()->setMessageFlags(translatedIndexes
, Imap::Mailbox::FlagNames::flagged
, value
? Imap::Mailbox::FLAG_ADD
: Imap::Mailbox::FLAG_REMOVE
);
1617 void MainWindow::handleMarkAsJunk(const bool value
)
1619 const QModelIndexList translatedIndexes
= translatedSelection();
1620 if (translatedIndexes
.isEmpty()) {
1621 qDebug() << "Model::handleMarkAsJunk: no valid messages";
1624 imapModel()->setMessageFlags(translatedIndexes
, Imap::Mailbox::FlagNames::notjunk
, Imap::Mailbox::FLAG_REMOVE
);
1626 imapModel()->setMessageFlags(translatedIndexes
, Imap::Mailbox::FlagNames::junk
, value
? Imap::Mailbox::FLAG_ADD
: Imap::Mailbox::FLAG_REMOVE
);
1630 void MainWindow::handleMarkAsNotJunk(const bool value
)
1632 const QModelIndexList translatedIndexes
= translatedSelection();
1633 if (translatedIndexes
.isEmpty()) {
1634 qDebug() << "Model::handleMarkAsNotJunk: no valid messages";
1637 imapModel()->setMessageFlags(translatedIndexes
, Imap::Mailbox::FlagNames::junk
, Imap::Mailbox::FLAG_REMOVE
);
1639 imapModel()->setMessageFlags(translatedIndexes
, Imap::Mailbox::FlagNames::notjunk
, value
? Imap::Mailbox::FLAG_ADD
: Imap::Mailbox::FLAG_REMOVE
);
1643 void MainWindow::slotMoveToArchiveFailed(const QString
&error
)
1645 // XXX disable busy cursor
1646 QMessageBox::critical(this, tr("Failed to archive"), error
);
1649 void MainWindow::handleMoveToArchive()
1651 const QModelIndexList translatedIndexes
= translatedSelection();
1652 if (translatedIndexes
.isEmpty()) {
1653 qDebug() << "Model::handleMoveToArchive: no valid messages";
1655 auto archiveFolderName
= m_settings
->value(Common::SettingsNames::imapArchiveFolderName
).toString();
1656 auto copyMoveMessagesTask
= imapModel()->copyMoveMessages(
1657 archiveFolderName
.isEmpty() ? Common::SettingsNames::imapDefaultArchiveFolderName
: archiveFolderName
,
1658 translatedIndexes
, Imap::Mailbox::CopyMoveOperation::MOVE
);
1659 connect(copyMoveMessagesTask
, &Imap::Mailbox::ImapTask::failed
, this, &MainWindow::slotMoveToArchiveFailed
);
1664 void MainWindow::slotExpunge()
1666 imapModel()->expungeMailbox(qobject_cast
<Imap::Mailbox::MsgListModel
*>(m_imapAccess
->msgListModel())->currentMailbox());
1669 void MainWindow::slotMarkCurrentMailboxRead()
1671 imapModel()->markMailboxAsRead(mboxTree
->currentIndex());
1674 void MainWindow::slotCreateMailboxBelowCurrent()
1676 createMailboxBelow(mboxTree
->currentIndex());
1679 void MainWindow::slotCreateTopMailbox()
1681 createMailboxBelow(QModelIndex());
1684 void MainWindow::createMailboxBelow(const QModelIndex
&index
)
1686 Imap::Mailbox::TreeItemMailbox
*mboxPtr
= index
.isValid() ?
1687 dynamic_cast<Imap::Mailbox::TreeItemMailbox
*>(
1688 Imap::Mailbox::Model::realTreeItem(index
)) :
1691 Ui::CreateMailboxDialog ui
;
1692 QDialog
*dialog
= new QDialog(this);
1695 dialog
->setWindowTitle(mboxPtr
?
1696 tr("Create a Subfolder of %1").arg(mboxPtr
->mailbox()) :
1697 tr("Create a Top-level Mailbox"));
1699 if (dialog
->exec() == QDialog::Accepted
) {
1702 parts
<< mboxPtr
->mailbox();
1703 parts
<< ui
.mailboxName
->text();
1704 if (ui
.otherMailboxes
->isChecked())
1706 QString targetName
= parts
.join(mboxPtr
? mboxPtr
->separator() : QString()); // FIXME: top-level separator
1707 imapModel()->createMailbox(targetName
,
1708 ui
.subscribe
->isChecked() ?
1709 Imap::Mailbox::AutoSubscription::SUBSCRIBE
:
1710 Imap::Mailbox::AutoSubscription::NO_EXPLICIT_SUBSCRIPTION
1715 void MainWindow::slotDeleteCurrentMailbox()
1717 if (! mboxTree
->currentIndex().isValid())
1720 QModelIndex mailbox
= Imap::deproxifiedIndex(mboxTree
->currentIndex());
1721 Q_ASSERT(mailbox
.isValid());
1722 QString name
= mailbox
.data(Imap::Mailbox::RoleMailboxName
).toString();
1724 if (QMessageBox::question(this, tr("Delete Mailbox"),
1725 tr("Are you sure to delete mailbox %1?").arg(name
),
1726 QMessageBox::Yes
| QMessageBox::No
) == QMessageBox::Yes
) {
1727 imapModel()->deleteMailbox(name
);
1731 void MainWindow::updateMessageFlags()
1733 updateMessageFlagsOf(QModelIndex());
1736 void MainWindow::updateMessageFlagsOf(const QModelIndex
&index
)
1738 QModelIndexList indexes
= index
.isValid() ? QModelIndexList() << index
: translatedSelection();
1739 const bool isValid
= !indexes
.isEmpty() &&
1740 // either we operate on the -already valided- selection or the index must be valid
1741 (!index
.isValid() || index
.data(Imap::Mailbox::RoleMessageUid
).toUInt() > 0);
1742 const bool okToModify
= imapModel()->isNetworkAvailable() && isValid
;
1744 markAsRead
->setEnabled(okToModify
);
1745 markAsDeleted
->setEnabled(okToModify
);
1746 markAsFlagged
->setEnabled(okToModify
);
1747 markAsJunk
->setEnabled(okToModify
);
1748 markAsNotJunk
->setEnabled(okToModify
);
1750 // There's no point in moving from Archive to, well, Archive
1751 auto archiveFolderName
= m_settings
->value(Common::SettingsNames::imapArchiveFolderName
).toString();
1752 if (archiveFolderName
.isEmpty()) {
1753 archiveFolderName
= Common::SettingsNames::imapDefaultArchiveFolderName
;
1755 moveToArchive
->setEnabled(okToModify
&&
1756 std::any_of(indexes
.cbegin(), indexes
.cend(),
1757 [archiveFolderName
](const QModelIndex
&i
) {
1758 return i
.data(Imap::Mailbox::RoleMailboxName
) != archiveFolderName
;
1761 tag1
->setEnabled(okToModify
);
1762 tag2
->setEnabled(okToModify
);
1763 tag3
->setEnabled(okToModify
);
1764 tag4
->setEnabled(okToModify
);
1765 tag5
->setEnabled(okToModify
);
1766 tag6
->setEnabled(okToModify
);
1767 tag7
->setEnabled(okToModify
);
1768 tag8
->setEnabled(okToModify
);
1769 tag9
->setEnabled(okToModify
);
1771 bool isRead
= isValid
,
1772 isDeleted
= isValid
,
1773 isFlagged
= isValid
,
1775 isNotJunk
= isValid
,
1785 auto updateTag
= [=](const QModelIndex
&i
, bool &hasTag
, int index
) {
1786 if (hasTag
&& !m_favoriteTags
->tagNameByIndex(index
).isEmpty() &&
1787 !i
.data(Imap::Mailbox::RoleMessageFlags
).toStringList().contains(m_favoriteTags
->tagNameByIndex(index
)))
1792 Q_FOREACH (const QModelIndex
&i
, indexes
) {
1793 #define UPDATE_STATE(PROP) \
1794 if (is##PROP && !i.data(Imap::Mailbox::RoleMessageIsMarked##PROP).toBool()) \
1797 UPDATE_STATE(Deleted
)
1798 UPDATE_STATE(Flagged
)
1800 UPDATE_STATE(NotJunk
)
1802 updateTag(i
, hasTag1
, 0);
1803 updateTag(i
, hasTag2
, 1);
1804 updateTag(i
, hasTag3
, 2);
1805 updateTag(i
, hasTag4
, 3);
1806 updateTag(i
, hasTag5
, 4);
1807 updateTag(i
, hasTag6
, 5);
1808 updateTag(i
, hasTag7
, 6);
1809 updateTag(i
, hasTag8
, 7);
1810 updateTag(i
, hasTag9
, 8);
1812 markAsRead
->setChecked(isRead
);
1813 markAsDeleted
->setChecked(isDeleted
);
1814 markAsFlagged
->setChecked(isFlagged
);
1815 markAsJunk
->setChecked(isJunk
&& !isNotJunk
);
1816 markAsNotJunk
->setChecked(isNotJunk
&& !isJunk
);
1818 tag1
->setChecked(hasTag1
);
1819 tag2
->setChecked(hasTag2
);
1820 tag3
->setChecked(hasTag3
);
1821 tag4
->setChecked(hasTag4
);
1822 tag5
->setChecked(hasTag5
);
1823 tag6
->setChecked(hasTag6
);
1824 tag7
->setChecked(hasTag7
);
1825 tag8
->setChecked(hasTag8
);
1826 tag9
->setChecked(hasTag9
);
1829 void MainWindow::updateActionsOnlineOffline(bool online
)
1831 reloadMboxList
->setEnabled(online
);
1832 resyncMbox
->setEnabled(online
);
1833 expunge
->setEnabled(online
);
1834 createChildMailbox
->setEnabled(online
);
1835 createTopMailbox
->setEnabled(online
);
1836 deleteCurrentMailbox
->setEnabled(online
);
1837 m_actionMarkMailboxAsRead
->setEnabled(online
);
1838 updateMessageFlags();
1839 showImapCapabilities
->setEnabled(online
);
1841 m_replyGuess
->setEnabled(false);
1842 m_replyPrivate
->setEnabled(false);
1843 m_replyAll
->setEnabled(false);
1844 m_replyAllButMe
->setEnabled(false);
1845 m_replyList
->setEnabled(false);
1846 m_forwardAsAttachment
->setEnabled(false);
1847 m_resend
->setEnabled(false);
1851 void MainWindow::slotUpdateMessageActions()
1853 Composer::RecipientList dummy
;
1854 m_replyPrivate
->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_PRIVATE
, senderIdentitiesModel(),
1855 m_messageWidget
->messageView
->currentMessage(), dummy
));
1856 m_replyAllButMe
->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_ALL_BUT_ME
, senderIdentitiesModel(),
1857 m_messageWidget
->messageView
->currentMessage(), dummy
));
1858 m_replyAll
->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_ALL
, senderIdentitiesModel(),
1859 m_messageWidget
->messageView
->currentMessage(), dummy
));
1860 m_replyList
->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_LIST
, senderIdentitiesModel(),
1861 m_messageWidget
->messageView
->currentMessage(), dummy
));
1862 m_replyGuess
->setEnabled(m_replyPrivate
->isEnabled() || m_replyAllButMe
->isEnabled()
1863 || m_replyAll
->isEnabled() || m_replyList
->isEnabled());
1865 // Check the default reply mode
1866 // I suspect this is not going to work for everybody. Suggestions welcome...
1867 if (m_replyList
->isEnabled()) {
1868 m_replyButton
->setDefaultAction(m_replyList
);
1869 } else if (m_replyAllButMe
->isEnabled()) {
1870 m_replyButton
->setDefaultAction(m_replyAllButMe
);
1872 m_replyButton
->setDefaultAction(m_replyPrivate
);
1875 m_forwardAsAttachment
->setEnabled(m_messageWidget
->messageView
->currentMessage().isValid());
1876 m_resend
->setEnabled(m_messageWidget
->messageView
->currentMessage().isValid());
1879 void MainWindow::scrollMessageUp()
1881 m_messageWidget
->area
->ensureVisible(0, 0, 0, 0);
1884 void MainWindow::slotReplyTo()
1886 m_messageWidget
->messageView
->reply(this, Composer::REPLY_PRIVATE
);
1889 void MainWindow::slotReplyAll()
1891 m_messageWidget
->messageView
->reply(this, Composer::REPLY_ALL
);
1894 void MainWindow::slotReplyAllButMe()
1896 m_messageWidget
->messageView
->reply(this, Composer::REPLY_ALL_BUT_ME
);
1899 void MainWindow::slotReplyList()
1901 m_messageWidget
->messageView
->reply(this, Composer::REPLY_LIST
);
1904 void MainWindow::slotReplyGuess()
1906 if (m_replyButton
->defaultAction() == m_replyAllButMe
) {
1907 slotReplyAllButMe();
1908 } else if (m_replyButton
->defaultAction() == m_replyAll
) {
1910 } else if (m_replyButton
->defaultAction() == m_replyList
) {
1917 void MainWindow::slotForwardAsAttachment()
1919 m_messageWidget
->messageView
->forward(this, Composer::ForwardMode::FORWARD_AS_ATTACHMENT
);
1922 void MainWindow::slotResend()
1925 Imap::Mailbox::Model::realTreeItem(msgListWidget
->tree
->currentIndex(), nullptr, &index
);
1926 if (!index
.isValid())
1929 auto recipients
= QList
<QPair
<Composer::RecipientKind
,QString
>>();
1930 for (const auto &kind
: {Imap::Mailbox::RoleMessageTo
, Imap::Mailbox::RoleMessageCc
, Imap::Mailbox::RoleMessageBcc
}) {
1931 for (const auto &oneAddr
: index
.data(kind
).toList()) {
1932 Q_ASSERT(oneAddr
.type() == QVariant::StringList
);
1933 QStringList item
= oneAddr
.toStringList();
1934 Q_ASSERT(item
.size() == 4);
1935 Imap::Message::MailAddress
a(item
[0], item
[1], item
[2], item
[3]);
1936 Composer::RecipientKind translatedKind
= Composer::RecipientKind::ADDRESS_TO
;
1938 case Imap::Mailbox::RoleMessageTo
:
1939 translatedKind
= Composer::RecipientKind::ADDRESS_RESENT_TO
;
1941 case Imap::Mailbox::RoleMessageCc
:
1942 translatedKind
= Composer::RecipientKind::ADDRESS_RESENT_CC
;
1944 case Imap::Mailbox::RoleMessageBcc
:
1945 translatedKind
= Composer::RecipientKind::ADDRESS_RESENT_BCC
;
1951 recipients
.push_back({translatedKind
, a
.asPrettyString()});
1955 ComposeWidget::warnIfMsaNotConfigured(
1956 ComposeWidget::createFromReadOnly(this, index
, recipients
),
1961 void MainWindow::slotComposeMailUrl(const QUrl
&url
)
1963 ComposeWidget::warnIfMsaNotConfigured(ComposeWidget::createFromUrl(this, url
), this);
1966 void MainWindow::slotManageContact(const QUrl
&url
)
1968 Imap::Message::MailAddress addr
;
1969 if (!Imap::Message::MailAddress::fromUrl(addr
, url
, QStringLiteral("x-trojita-manage-contact")))
1972 Plugins::AddressbookPlugin
*addressbook
= pluginManager()->addressbook();
1976 addressbook
->openContactWindow(addr
.mailbox
+ QLatin1Char('@') + addr
.host
, addr
.name
);
1979 void MainWindow::invokeContactEditor()
1981 Plugins::AddressbookPlugin
*addressbook
= pluginManager()->addressbook();
1985 addressbook
->openAddressbookWindow();
1988 /** @short Create an MSAFactory as per the settings */
1989 MSA::MSAFactory
*MainWindow::msaFactory()
1991 using namespace Common
;
1992 QString method
= m_settings
->value(SettingsNames::msaMethodKey
).toString();
1993 MSA::MSAFactory
*msaFactory
= 0;
1994 if (method
== SettingsNames::methodSMTP
|| method
== SettingsNames::methodSSMTP
) {
1995 msaFactory
= new MSA::SMTPFactory(m_settings
->value(SettingsNames::smtpHostKey
).toString(),
1996 m_settings
->value(SettingsNames::smtpPortKey
).toInt(),
1997 (method
== SettingsNames::methodSSMTP
),
1998 (method
== SettingsNames::methodSMTP
)
1999 && m_settings
->value(SettingsNames::smtpStartTlsKey
).toBool(),
2000 m_settings
->value(SettingsNames::smtpAuthKey
).toBool(),
2001 m_settings
->value(SettingsNames::smtpAuthReuseImapCredsKey
, false).toBool() ?
2002 m_settings
->value(SettingsNames::imapUserKey
).toString() :
2003 m_settings
->value(SettingsNames::smtpUserKey
).toString());
2004 } else if (method
== SettingsNames::methodSENDMAIL
) {
2005 QStringList args
= m_settings
->value(SettingsNames::sendmailKey
, SettingsNames::sendmailDefaultCmd
).toString().split(QLatin1Char(' '));
2006 if (args
.isEmpty()) {
2009 QString appName
= args
.takeFirst();
2010 msaFactory
= new MSA::SendmailFactory(appName
, args
);
2011 } else if (method
== SettingsNames::methodImapSendmail
) {
2012 if (!imapModel()->capabilities().contains(QStringLiteral("X-DRAFT-I01-SENDMAIL"))) {
2015 msaFactory
= new MSA::ImapSubmitFactory(qobject_cast
<Imap::Mailbox::Model
*>(imapAccess()->imapModel()));
2022 void MainWindow::slotMailboxDeleteFailed(const QString
&mailbox
, const QString
&msg
)
2024 Gui::Util::messageBoxWarning(this, tr("Can't delete mailbox"),
2025 tr("Deleting mailbox \"%1\" failed with the following message:\n%2").arg(mailbox
, msg
));
2028 void MainWindow::slotMailboxCreateFailed(const QString
&mailbox
, const QString
&msg
)
2030 Gui::Util::messageBoxWarning(this, tr("Can't create mailbox"),
2031 tr("Creating mailbox \"%1\" failed with the following message:\n%2").arg(mailbox
, msg
));
2034 void MainWindow::slotMailboxSyncFailed(const QString
&mailbox
, const QString
&msg
)
2036 Gui::Util::messageBoxWarning(this, tr("Can't open mailbox"),
2037 tr("Opening mailbox \"%1\" failed with the following message:\n%2").arg(mailbox
, msg
));
2040 void MainWindow::slotMailboxChanged(const QModelIndex
&mailbox
)
2042 using namespace Imap::Mailbox
;
2043 QString mailboxName
= mailbox
.data(RoleMailboxName
).toString();
2044 bool isSentMailbox
= mailbox
.isValid() && !mailboxName
.isEmpty() &&
2045 m_settings
->value(Common::SettingsNames::composerSaveToImapKey
).toBool() &&
2046 mailboxName
== m_settings
->value(Common::SettingsNames::composerImapSentKey
).toString();
2047 QTreeView
*tree
= msgListWidget
->tree
;
2049 // Automatically trigger visibility of the TO and FROM columns
2050 if (isSentMailbox
) {
2051 if (tree
->isColumnHidden(MsgListModel::TO
) && !tree
->isColumnHidden(MsgListModel::FROM
)) {
2052 tree
->hideColumn(MsgListModel::FROM
);
2053 tree
->showColumn(MsgListModel::TO
);
2056 if (tree
->isColumnHidden(MsgListModel::FROM
) && !tree
->isColumnHidden(MsgListModel::TO
)) {
2057 tree
->hideColumn(MsgListModel::TO
);
2058 tree
->showColumn(MsgListModel::FROM
);
2062 updateMessageFlags();
2063 slotScrollToUnseenMessage();
2066 void MainWindow::showConnectionStatus(uint parserId
, Imap::ConnectionState state
)
2069 static Imap::ConnectionState previousState
= Imap::ConnectionState::CONN_STATE_NONE
;
2070 QString message
= connectionStateToString(state
);
2072 if (state
== Imap::ConnectionState::CONN_STATE_SELECTED
&& previousState
>= Imap::ConnectionState::CONN_STATE_SELECTED
) {
2073 // prevent excessive popups when we "reset the state" to something which is shown quite often
2074 showStatusMessage(QString());
2076 showStatusMessage(message
);
2078 previousState
= state
;
2081 void MainWindow::slotShowLinkTarget(const QString
&link
)
2083 if (link
.isEmpty()) {
2084 QToolTip::hideText();
2086 QToolTip::showText(QCursor::pos(), tr("Link target: %1").arg(UiUtils::Formatting::htmlEscaped(link
)));
2090 void MainWindow::slotShowAboutTrojita()
2093 QDialog
*widget
= new QDialog(this);
2094 widget
->setAttribute(Qt::WA_DeleteOnClose
);
2096 ui
.versionLabel
->setText(Common::Application::version
);
2097 ui
.qtVersion
->setText(QStringLiteral("<a href=\"about-qt\">Qt " QT_VERSION_STR
"</a>"));
2098 connect(ui
.qtVersion
, &QLabel::linkActivated
, qApp
, &QApplication::aboutQt
);
2100 std::vector
<std::pair
<QString
, bool>> features
;
2101 features
.emplace_back(tr("Plugins"),
2102 #ifdef WITH_SHARED_PLUGINS
2108 features
.emplace_back(tr("Encrypted and signed messages"),
2109 #ifdef TROJITA_HAVE_CRYPTO_MESSAGES
2115 features
.emplace_back(tr("IMAP compression"),
2116 #ifdef TROJITA_HAVE_ZLIB
2123 QString featuresText
= QStringLiteral("<ul>");
2124 for (const auto x
: features
) {
2125 featuresText
+= x
.second
?
2126 tr("<li>%1: supported</li>").arg(x
.first
)
2127 : tr("<li>%1: <strong>disabled</strong></li>").arg(x
.first
);
2129 featuresText
+= QStringLiteral("</ul>");
2130 ui
.descriptionLabel
->setText(ui
.descriptionLabel
->text() + featuresText
);
2132 QStringList copyright
;
2134 // Find the names of the authors and remove date codes from there
2135 QFile
license(QStringLiteral(":/LICENSE"));
2136 license
.open(QFile::ReadOnly
);
2137 const QString
prefix(QStringLiteral("Copyright (C) "));
2138 Q_FOREACH(const QString
&line
, QString::fromUtf8(license
.readAll()).split(QLatin1Char('\n'))) {
2139 if (line
.startsWith(prefix
)) {
2140 const int pos
= prefix
.size();
2141 copyright
<< QChar(0xa9 /* COPYRIGHT SIGN */) + QLatin1Char(' ') +
2142 line
.mid(pos
).replace(QRegularExpression(QLatin1String("(\\d) - (\\d)")),
2143 QLatin1String("\\1") + QChar(0x2014 /* EM DASH */) + QLatin1String("\\2"));
2147 ui
.credits
->setTextFormat(Qt::PlainText
);
2148 ui
.credits
->setText(copyright
.join(QStringLiteral("\n")));
2152 void MainWindow::slotDonateToTrojita()
2154 QDesktopServices::openUrl(QStringLiteral("https://sourceforge.net/p/trojita/donate/"));
2157 void MainWindow::slotSaveCurrentMessageBody()
2159 Q_FOREACH(const QModelIndex
&item
, msgListWidget
->tree
->selectionModel()->selectedIndexes()) {
2160 Q_ASSERT(item
.isValid());
2161 if (item
.column() != 0)
2163 if (! item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
2166 QModelIndex messageIndex
= Imap::deproxifiedIndex(item
);
2168 Imap::Network::MsgPartNetAccessManager
*netAccess
= new Imap::Network::MsgPartNetAccessManager(this);
2169 netAccess
->setModelMessage(messageIndex
);
2170 Imap::Network::FileDownloadManager
*fileDownloadManager
=
2171 new Imap::Network::FileDownloadManager(this, netAccess
, messageIndex
);
2172 connect(fileDownloadManager
, &Imap::Network::FileDownloadManager::succeeded
, fileDownloadManager
, &QObject::deleteLater
);
2173 connect(fileDownloadManager
, &Imap::Network::FileDownloadManager::transferError
, fileDownloadManager
, &QObject::deleteLater
);
2174 connect(fileDownloadManager
, &Imap::Network::FileDownloadManager::fileNameRequested
,
2175 this, &MainWindow::slotDownloadMessageFileNameRequested
);
2176 connect(fileDownloadManager
, &Imap::Network::FileDownloadManager::transferError
,
2177 this, &MainWindow::slotDownloadTransferError
);
2178 connect(fileDownloadManager
, &QObject::destroyed
, netAccess
, &QObject::deleteLater
);
2179 fileDownloadManager
->downloadMessage();
2183 void MainWindow::slotDownloadTransferError(const QString
&errorString
)
2185 Gui::Util::messageBoxCritical(this, tr("Can't save into file"),
2186 tr("Unable to save into file. Error:\n%1").arg(errorString
));
2189 void MainWindow::slotDownloadMessageFileNameRequested(QString
*fileName
)
2191 *fileName
= QFileDialog::getSaveFileName(this, tr("Save Message"), *fileName
, QString(), 0,
2192 QFileDialog::HideNameFilterDetails
);
2195 void MainWindow::slotViewMsgSource()
2197 Q_FOREACH(const QModelIndex
&item
, msgListWidget
->tree
->selectionModel()->selectedIndexes()) {
2198 Q_ASSERT(item
.isValid());
2199 if (item
.column() != 0)
2201 if (! item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
2203 auto w
= messageSourceWidget(item
);
2204 //: Translators: %1 is the UID of a message (a number) and %2 is the name of a mailbox.
2205 w
->setWindowTitle(tr("Message source of UID %1 in %2").arg(
2206 QString::number(item
.data(Imap::Mailbox::RoleMessageUid
).toUInt()),
2207 Imap::deproxifiedIndex(item
).parent().parent().data(Imap::Mailbox::RoleMailboxName
).toString()
2213 QWidget
*MainWindow::messageSourceWidget(const QModelIndex
&message
)
2215 QModelIndex messageIndex
= Imap::deproxifiedIndex(message
);
2216 MessageSourceWidget
*sourceWidget
= new MessageSourceWidget(0, messageIndex
);
2217 sourceWidget
->setAttribute(Qt::WA_DeleteOnClose
);
2218 QAction
*close
= new QAction(UiUtils::loadIcon(QStringLiteral("window-close")), tr("Close"), sourceWidget
);
2219 sourceWidget
->addAction(close
);
2220 close
->setShortcut(tr("Ctrl+W"));
2221 connect(close
, &QAction::triggered
, sourceWidget
, &QWidget::close
);
2222 return sourceWidget
;
2225 void MainWindow::slotViewMsgHeaders()
2227 Q_FOREACH(const QModelIndex
&item
, msgListWidget
->tree
->selectionModel()->selectedIndexes()) {
2228 Q_ASSERT(item
.isValid());
2229 if (item
.column() != 0)
2231 if (! item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
2233 QModelIndex messageIndex
= Imap::deproxifiedIndex(item
);
2235 auto widget
= new MessageHeadersWidget(nullptr, messageIndex
);
2236 widget
->setAttribute(Qt::WA_DeleteOnClose
);
2237 QAction
*close
= new QAction(UiUtils::loadIcon(QStringLiteral("window-close")), tr("Close"), widget
);
2238 widget
->addAction(close
);
2239 close
->setShortcut(tr("Ctrl+W"));
2240 connect(close
, &QAction::triggered
, widget
, &QWidget::close
);
2245 #ifdef XTUPLE_CONNECT
2246 void MainWindow::slotXtSyncCurrentMailbox()
2248 QModelIndex index
= mboxTree
->currentIndex();
2249 if (! index
.isValid())
2252 QString mailbox
= index
.data(Imap::Mailbox::RoleMailboxName
).toString();
2254 QStringList mailboxes
= s
.value(Common::SettingsNames::xtSyncMailboxList
).toStringList();
2255 if (xtIncludeMailboxInSync
->isChecked()) {
2256 if (! mailboxes
.contains(mailbox
)) {
2257 mailboxes
.append(mailbox
);
2260 mailboxes
.removeAll(mailbox
);
2262 s
.setValue(Common::SettingsNames::xtSyncMailboxList
, mailboxes
);
2263 QSettings(QSettings::UserScope
, QString::fromAscii("xTuple.com"), QString::fromAscii("xTuple")).setValue(Common::SettingsNames::xtSyncMailboxList
, mailboxes
);
2264 prettyMboxModel
->xtConnectStatusChanged(index
);
2268 void MainWindow::slotSubscribeCurrentMailbox()
2270 QModelIndex index
= mboxTree
->currentIndex();
2271 if (! index
.isValid())
2274 QString mailbox
= index
.data(Imap::Mailbox::RoleMailboxName
).toString();
2275 if (m_actionSubscribeMailbox
->isChecked()) {
2276 imapModel()->subscribeMailbox(mailbox
);
2278 imapModel()->unsubscribeMailbox(mailbox
);
2282 void MainWindow::slotShowOnlySubscribed()
2284 if (m_actionShowOnlySubscribed
->isEnabled()) {
2285 m_settings
->setValue(Common::SettingsNames::guiMailboxListShowOnlySubscribed
, m_actionShowOnlySubscribed
->isChecked());
2286 prettyMboxModel
->setShowOnlySubscribed(m_actionShowOnlySubscribed
->isChecked());
2290 void MainWindow::slotScrollToUnseenMessage()
2292 // Now this is much, much more reliable than messing around with finding out an "interesting message"...
2293 if (!m_actionSortNone
->isChecked() && !m_actionSortThreading
->isChecked()) {
2294 // we're using some funky sorting, better don't scroll anywhere
2296 if (m_actionSortDescending
->isChecked()) {
2297 msgListWidget
->tree
->scrollToTop();
2299 msgListWidget
->tree
->scrollToBottom();
2303 void MainWindow::slotScrollToCurrent()
2305 // TODO: begs for lambda
2306 if (QScrollBar
*vs
= msgListWidget
->tree
->verticalScrollBar()) {
2307 vs
->setValue(vs
->maximum() - vs
->value()); // implies vs->minimum() == 0
2311 void MainWindow::slotThreadMsgList()
2313 // We want to save user's preferences and not override them with "threading disabled" when the server
2314 // doesn't report them, like in initial greetings. That's why we have to check for isEnabled() here.
2315 const bool useThreading
= actionThreadMsgList
->isChecked();
2317 // Switching between threaded/unthreaded view shall reset the sorting criteria. The goal is to make
2318 // sorting rather seldomly used as people shall instead use proper threading.
2320 m_actionSortThreading
->setEnabled(true);
2321 if (!m_actionSortThreading
->isChecked())
2322 m_actionSortThreading
->trigger();
2323 m_actionSortNone
->setEnabled(false);
2325 m_actionSortNone
->setEnabled(true);
2326 if (!m_actionSortNone
->isChecked())
2327 m_actionSortNone
->trigger();
2328 m_actionSortThreading
->setEnabled(false);
2331 QPersistentModelIndex currentItem
= msgListWidget
->tree
->currentIndex();
2333 if (useThreading
&& actionThreadMsgList
->isEnabled()) {
2334 msgListWidget
->tree
->setRootIsDecorated(true);
2335 qobject_cast
<Imap::Mailbox::ThreadingMsgListModel
*>(m_imapAccess
->threadingMsgListModel())->setUserWantsThreading(true);
2337 msgListWidget
->tree
->setRootIsDecorated(false);
2338 qobject_cast
<Imap::Mailbox::ThreadingMsgListModel
*>(m_imapAccess
->threadingMsgListModel())->setUserWantsThreading(false);
2340 m_settings
->setValue(Common::SettingsNames::guiMsgListShowThreading
, QVariant(useThreading
));
2342 if (currentItem
.isValid()) {
2343 msgListWidget
->tree
->scrollTo(currentItem
);
2345 // If we cannot determine the current item, at least scroll to a predictable place. Without this, the view
2346 // would jump to "weird" places, probably due to some heuristics about trying to show "roughly the same"
2347 // objects as what was visible before the reshuffling.
2348 slotScrollToUnseenMessage();
2352 void MainWindow::slotSortingPreferenceChanged()
2354 Qt::SortOrder order
= m_actionSortDescending
->isChecked() ? Qt::DescendingOrder
: Qt::AscendingOrder
;
2356 using namespace Imap::Mailbox
;
2359 if (m_actionSortByArrival
->isChecked()) {
2360 column
= MsgListModel::RECEIVED_DATE
;
2361 } else if (m_actionSortByCc
->isChecked()) {
2362 column
= MsgListModel::CC
;
2363 } else if (m_actionSortByDate
->isChecked()) {
2364 column
= MsgListModel::DATE
;
2365 } else if (m_actionSortByFrom
->isChecked()) {
2366 column
= MsgListModel::FROM
;
2367 } else if (m_actionSortBySize
->isChecked()) {
2368 column
= MsgListModel::SIZE
;
2369 } else if (m_actionSortBySubject
->isChecked()) {
2370 column
= MsgListModel::SUBJECT
;
2371 } else if (m_actionSortByTo
->isChecked()) {
2372 column
= MsgListModel::TO
;
2377 msgListWidget
->tree
->header()->setSortIndicator(column
, order
);
2380 void MainWindow::slotSortingConfirmed(int column
, Qt::SortOrder order
)
2382 // don't do anything during initialization
2383 if (!m_actionSortNone
)
2386 using namespace Imap::Mailbox
;
2390 case MsgListModel::SEEN
:
2391 case MsgListModel::FLAGGED
:
2392 case MsgListModel::ATTACHMENT
:
2393 case MsgListModel::COLUMN_COUNT
:
2394 case MsgListModel::BCC
:
2396 if (actionThreadMsgList
->isChecked())
2397 action
= m_actionSortThreading
;
2399 action
= m_actionSortNone
;
2401 case MsgListModel::SUBJECT
:
2402 action
= m_actionSortBySubject
;
2404 case MsgListModel::FROM
:
2405 action
= m_actionSortByFrom
;
2407 case MsgListModel::TO
:
2408 action
= m_actionSortByTo
;
2410 case MsgListModel::CC
:
2411 action
= m_actionSortByCc
;
2413 case MsgListModel::DATE
:
2414 action
= m_actionSortByDate
;
2416 case MsgListModel::RECEIVED_DATE
:
2417 action
= m_actionSortByArrival
;
2419 case MsgListModel::SIZE
:
2420 action
= m_actionSortBySize
;
2423 action
= m_actionSortNone
;
2426 action
->setChecked(true);
2427 if (order
== Qt::DescendingOrder
)
2428 m_actionSortDescending
->setChecked(true);
2430 m_actionSortAscending
->setChecked(true);
2433 void MainWindow::slotSearchRequested(const QStringList
&searchConditions
)
2435 Imap::Mailbox::ThreadingMsgListModel
* threadingMsgListModel
=
2436 qobject_cast
<Imap::Mailbox::ThreadingMsgListModel
*>(m_imapAccess
->threadingMsgListModel());
2437 threadingMsgListModel
->setUserSearchingSortingPreference(searchConditions
, threadingMsgListModel
->currentSortCriterium(),
2438 threadingMsgListModel
->currentSortOrder());
2441 void MainWindow::slotHideRead()
2443 const bool hideRead
= actionHideRead
->isChecked();
2444 prettyMsgListModel
->setHideRead(hideRead
);
2445 m_settings
->setValue(Common::SettingsNames::guiMsgListHideRead
, QVariant(hideRead
));
2448 void MainWindow::slotCapabilitiesUpdated(const QStringList
&capabilities
)
2450 msgListWidget
->tree
->header()->viewport()->removeEventFilter(this);
2451 if (capabilities
.contains(QStringLiteral("SORT"))) {
2452 m_actionSortByDate
->actionGroup()->setEnabled(true);
2454 m_actionSortByDate
->actionGroup()->setEnabled(false);
2455 msgListWidget
->tree
->header()->viewport()->installEventFilter(this);
2458 msgListWidget
->setFuzzySearchSupported(capabilities
.contains(QStringLiteral("SEARCH=FUZZY")));
2460 m_actionShowOnlySubscribed
->setEnabled(capabilities
.contains(QStringLiteral("LIST-EXTENDED")));
2461 m_actionShowOnlySubscribed
->setChecked(m_actionShowOnlySubscribed
->isEnabled() &&
2463 Common::SettingsNames::guiMailboxListShowOnlySubscribed
, false).toBool());
2464 m_actionSubscribeMailbox
->setEnabled(m_actionShowOnlySubscribed
->isEnabled());
2466 const QStringList supportedCapabilities
= Imap::Mailbox::ThreadingMsgListModel::supportedCapabilities();
2467 Q_FOREACH(const QString
&capability
, capabilities
) {
2468 if (supportedCapabilities
.contains(capability
)) {
2469 actionThreadMsgList
->setEnabled(true);
2470 if (actionThreadMsgList
->isChecked())
2471 slotThreadMsgList();
2475 actionThreadMsgList
->setEnabled(false);
2478 void MainWindow::slotShowImapInfo()
2481 Q_FOREACH(const QString
&cap
, imapModel()->capabilities()) {
2482 caps
+= tr("<li>%1</li>\n").arg(cap
);
2486 if (!imapModel()->serverId().isEmpty() && imapModel()->capabilities().contains(QStringLiteral("ID"))) {
2487 QMap
<QByteArray
,QByteArray
> serverId
= imapModel()->serverId();
2489 #define IMAP_ID_FIELD(Var, Name) bool has_##Var = serverId.contains(Name); \
2490 QString Var = has_##Var ? QString::fromUtf8(serverId[Name]).toHtmlEscaped() : tr("Unknown");
2491 IMAP_ID_FIELD(serverName
, "name");
2492 IMAP_ID_FIELD(serverVersion
, "version");
2493 IMAP_ID_FIELD(os
, "os");
2494 IMAP_ID_FIELD(osVersion
, "os-version");
2495 IMAP_ID_FIELD(vendor
, "vendor");
2496 IMAP_ID_FIELD(supportUrl
, "support-url");
2497 IMAP_ID_FIELD(address
, "address");
2498 IMAP_ID_FIELD(date
, "date");
2499 IMAP_ID_FIELD(command
, "command");
2500 IMAP_ID_FIELD(arguments
, "arguments");
2501 IMAP_ID_FIELD(environment
, "environment");
2502 #undef IMAP_ID_FIELD
2503 if (has_serverName
) {
2504 idString
= tr("<p>");
2505 if (has_serverVersion
)
2506 idString
+= tr("Server: %1 %2").arg(serverName
, serverVersion
);
2508 idString
+= tr("Server: %1").arg(serverName
);
2511 idString
+= tr(" (%1)").arg(vendor
);
2515 idString
+= tr(" on %1 %2", "%1 is the operating system of an IMAP server and %2 is its version.").arg(os
, osVersion
);
2517 idString
+= tr(" on %1", "%1 is the operationg system of an IMAP server.").arg(os
);
2519 idString
+= tr("</p>");
2521 idString
= tr("<p>The IMAP server did not return usable information about itself.</p>");
2524 for (QMap
<QByteArray
,QByteArray
>::const_iterator it
= serverId
.constBegin(); it
!= serverId
.constEnd(); ++it
) {
2525 fullId
+= tr("<li>%1: %2</li>").arg(QString::fromUtf8(it
.key()).toHtmlEscaped(), QString::fromUtf8(it
.value()).toHtmlEscaped());
2527 idString
+= tr("<ul>%1</ul>").arg(fullId
);
2529 idString
= tr("<p>The server has not provided information about its software version.</p>");
2532 QMessageBox::information(this, tr("IMAP Server Information"),
2534 "<p>The following capabilities are currently advertised:</p>\n"
2535 "<ul>\n%2</ul>").arg(idString
, caps
));
2538 QSize
MainWindow::sizeHint() const
2540 return QSize(1150, 980);
2543 void MainWindow::slotUpdateWindowTitle()
2545 QModelIndex mailbox
= qobject_cast
<Imap::Mailbox::MsgListModel
*>(m_imapAccess
->msgListModel())->currentMailbox();
2546 QString profileName
= QString::fromUtf8(qgetenv("TROJITA_PROFILE"));
2547 if (!profileName
.isEmpty())
2548 profileName
= QLatin1String(" [") + profileName
+ QLatin1Char(']');
2549 if (mailbox
.isValid()) {
2550 if (mailbox
.data(Imap::Mailbox::RoleUnreadMessageCount
).toInt()) {
2551 setWindowTitle(tr("%1 - %n unread - Trojitá", 0, mailbox
.data(Imap::Mailbox::RoleUnreadMessageCount
).toInt())
2552 .arg(mailbox
.data(Imap::Mailbox::RoleShortMailboxName
).toString()) + profileName
);
2554 setWindowTitle(tr("%1 - Trojitá").arg(mailbox
.data(Imap::Mailbox::RoleShortMailboxName
).toString()) + profileName
);
2557 setWindowTitle(tr("Trojitá") + profileName
);
2561 void MainWindow::slotLayoutCompact()
2563 saveSizesAndState();
2564 if (!m_mainHSplitter
) {
2565 m_mainHSplitter
= new QSplitter();
2566 connect(m_mainHSplitter
.data(), &QSplitter::splitterMoved
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
2567 connect(m_mainHSplitter
.data(), &QSplitter::splitterMoved
, this, &MainWindow::possiblyLoadMessageOnSplittersChanged
);
2569 if (!m_mainVSplitter
) {
2570 m_mainVSplitter
= new QSplitter();
2571 m_mainVSplitter
->setOrientation(Qt::Vertical
);
2572 connect(m_mainVSplitter
.data(), &QSplitter::splitterMoved
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
2573 connect(m_mainVSplitter
.data(), &QSplitter::splitterMoved
, this, &MainWindow::possiblyLoadMessageOnSplittersChanged
);
2576 m_mainVSplitter
->addWidget(msgListWidget
);
2577 m_mainVSplitter
->addWidget(m_messageWidget
);
2578 m_mainHSplitter
->addWidget(mboxTree
);
2579 m_mainHSplitter
->addWidget(m_mainVSplitter
);
2582 msgListWidget
->show();
2583 m_messageWidget
->show();
2584 m_mainVSplitter
->show();
2585 m_mainHSplitter
->show();
2587 // The mboxTree shall not expand...
2588 m_mainHSplitter
->setStretchFactor(0, 0);
2589 // ...while the msgListTree shall consume all the remaining space
2590 m_mainHSplitter
->setStretchFactor(1, 1);
2591 // The CompleteMessageWidget shall not not collapse
2592 m_mainVSplitter
->setCollapsible(m_mainVSplitter
->indexOf(m_messageWidget
), false);
2594 setCentralWidget(m_mainHSplitter
);
2598 m_layoutMode
= LAYOUT_COMPACT
;
2599 m_settings
->setValue(Common::SettingsNames::guiMainWindowLayout
, Common::SettingsNames::guiMainWindowLayoutCompact
);
2600 applySizesAndState();
2603 void MainWindow::slotLayoutWide()
2605 saveSizesAndState();
2606 if (!m_mainHSplitter
) {
2607 m_mainHSplitter
= new QSplitter();
2608 connect(m_mainHSplitter
.data(), &QSplitter::splitterMoved
, m_delayedStateSaving
, static_cast<void (QTimer::*)()>(&QTimer::start
));
2609 connect(m_mainHSplitter
.data(), &QSplitter::splitterMoved
, this, &MainWindow::possiblyLoadMessageOnSplittersChanged
);
2612 m_mainHSplitter
->addWidget(mboxTree
);
2613 m_mainHSplitter
->addWidget(msgListWidget
);
2614 m_mainHSplitter
->addWidget(m_messageWidget
);
2615 msgListWidget
->resize(mboxTree
->size());
2616 m_messageWidget
->resize(mboxTree
->size());
2617 m_mainHSplitter
->setStretchFactor(0, 0);
2618 m_mainHSplitter
->setStretchFactor(1, 1);
2619 m_mainHSplitter
->setStretchFactor(2, 1);
2621 m_mainHSplitter
->setCollapsible(m_mainHSplitter
->indexOf(m_messageWidget
), false);
2624 msgListWidget
->show();
2625 m_messageWidget
->show();
2626 m_mainHSplitter
->show();
2628 setCentralWidget(m_mainHSplitter
);
2631 delete m_mainVSplitter
;
2633 m_layoutMode
= LAYOUT_WIDE
;
2634 m_settings
->setValue(Common::SettingsNames::guiMainWindowLayout
, Common::SettingsNames::guiMainWindowLayoutWide
);
2635 applySizesAndState();
2638 void MainWindow::slotLayoutOneAtTime()
2640 saveSizesAndState();
2644 m_mainStack
= new OnePanelAtTimeWidget(this, mboxTree
, msgListWidget
, m_messageWidget
, m_mainToolbar
, m_oneAtTimeGoBack
);
2645 setCentralWidget(m_mainStack
);
2647 delete m_mainHSplitter
;
2648 delete m_mainVSplitter
;
2650 m_layoutMode
= LAYOUT_ONE_AT_TIME
;
2651 m_settings
->setValue(Common::SettingsNames::guiMainWindowLayout
, Common::SettingsNames::guiMainWindowLayoutOneAtTime
);
2652 applySizesAndState();
2655 Imap::Mailbox::Model
*MainWindow::imapModel() const
2657 return qobject_cast
<Imap::Mailbox::Model
*>(m_imapAccess
->imapModel());
2660 void MainWindow::desktopGeometryChanged()
2662 m_delayedStateSaving
->start();
2665 QString
MainWindow::settingsKeyForLayout(const LayoutMode layout
)
2668 case LAYOUT_COMPACT
:
2669 return Common::SettingsNames::guiSizesInMainWinWhenCompact
;
2671 return Common::SettingsNames::guiSizesInMainWinWhenWide
;
2672 case LAYOUT_ONE_AT_TIME
:
2673 return Common::SettingsNames::guiSizesInaMainWinWhenOneAtATime
;
2679 void MainWindow::saveSizesAndState()
2681 if (m_skipSavingOfUI
)
2684 QRect geometry
= qApp
->desktop()->availableGeometry(this);
2685 QString key
= settingsKeyForLayout(m_layoutMode
);
2689 QList
<QByteArray
> items
;
2690 items
<< saveGeometry();
2691 items
<< saveState();
2692 items
<< (m_mainVSplitter
? m_mainVSplitter
->saveState() : QByteArray());
2693 items
<< (m_mainHSplitter
? m_mainHSplitter
->saveState() : QByteArray());
2694 items
<< msgListWidget
->tree
->header()->saveState();
2695 items
<< QByteArray::number(msgListWidget
->tree
->header()->count());
2696 for (int i
= 0; i
< msgListWidget
->tree
->header()->count(); ++i
) {
2697 items
<< QByteArray::number(msgListWidget
->tree
->header()->sectionSize(i
));
2699 // a bool cannot be pushed directly onto a QByteArray so we must convert it to a number
2700 items
<< QByteArray::number(menuBar()->isVisible());
2702 QDataStream
stream(&buf
, QIODevice::WriteOnly
);
2703 stream
<< items
.size();
2704 Q_FOREACH(const QByteArray
&item
, items
) {
2708 m_settings
->setValue(key
.arg(QString::number(geometry
.width())), buf
);
2711 void MainWindow::saveRawStateSetting(bool enabled
)
2713 m_settings
->setValue(Common::SettingsNames::guiAllowRawSearch
, enabled
);
2716 void MainWindow::applySizesAndState()
2718 QRect geometry
= qApp
->desktop()->availableGeometry(this);
2719 QString key
= settingsKeyForLayout(m_layoutMode
);
2723 QByteArray buf
= m_settings
->value(key
.arg(QString::number(geometry
.width()))).toByteArray();
2728 QDataStream
stream(&buf
, QIODevice::ReadOnly
);
2732 // There are slots connected to various events triggered by both restoreGeometry() and restoreState() which would attempt to
2733 // save our geometries and state, which is what we must avoid while this method is executing.
2734 bool skipSaving
= m_skipSavingOfUI
;
2735 m_skipSavingOfUI
= true;
2737 if (size
-- && !stream
.atEnd()) {
2740 // https://bugreports.qt-project.org/browse/QTBUG-30636
2741 if (windowState() & Qt::WindowMaximized
) {
2742 // restoreGeometry(.) restores the wrong size for at least maximized window
2743 // However, QWidget does also not notice that the configure request for this
2744 // is ignored by many window managers (because users really don't like when windows
2745 // drop themselves out of maximization) and has a wrong QWidget::geometry() idea from
2746 // the wrong assumption the request would have been hononred.
2747 // So we just "fix" the internal geometry immediately afterwards to prevent
2749 // There's atm. no flicker due to this (and because Qt compresses events)
2750 // In case it ever occurs, we can frame this in setUpdatesEnabled(false/true)
2751 QRect oldGeometry
= MainWindow::geometry();
2752 restoreGeometry(item
);
2753 if (windowState() & Qt::WindowMaximized
)
2754 setGeometry(oldGeometry
);
2756 restoreGeometry(item
);
2757 if (windowState() & Qt::WindowMaximized
) {
2758 // ensure to try setting the proper geometry and have the WM constrain us
2759 setGeometry(QApplication::desktop()->availableGeometry());
2764 if (size
-- && !stream
.atEnd()) {
2769 if (size
-- && !stream
.atEnd()) {
2771 if (m_mainVSplitter
) {
2772 m_mainVSplitter
->restoreState(item
);
2776 if (size
-- && !stream
.atEnd()) {
2778 if (m_mainHSplitter
) {
2779 m_mainHSplitter
->restoreState(item
);
2783 if (size
-- && !stream
.atEnd()) {
2785 msgListWidget
->tree
->header()->restoreState(item
);
2786 // got to manually update the state of the actions which control the visibility state
2787 msgListWidget
->tree
->updateActionsAfterRestoredState();
2790 connect(msgListWidget
->tree
->header(), &QHeaderView::sectionCountChanged
, msgListWidget
->tree
, &MsgListView::slotHandleNewColumns
);
2792 if (size
-- && !stream
.atEnd()) {
2795 int columns
= item
.toInt(&ok
);
2797 msgListWidget
->tree
->header()->setStretchLastSection(false);
2798 for (int i
= 0; i
< columns
&& size
-- && !stream
.atEnd(); ++i
) {
2800 int sectionSize
= item
.toInt();
2801 QHeaderView::ResizeMode resizeMode
= msgListWidget
->tree
->resizeModeForColumn(i
);
2802 if (sectionSize
> 0 && resizeMode
== QHeaderView::Interactive
) {
2803 // fun fact: user cannot resize by mouse when size <= 0
2804 msgListWidget
->tree
->setColumnWidth(i
, sectionSize
);
2806 msgListWidget
->tree
->setColumnWidth(i
, msgListWidget
->tree
->sizeHintForColumn(i
));
2808 msgListWidget
->tree
->header()->setSectionResizeMode(i
, resizeMode
);
2813 if (size
-- && !stream
.atEnd()) {
2816 bool visibility
= item
.toInt(&ok
);
2818 menuBar()->setVisible(visibility
);
2819 showMenuBar
->setChecked(visibility
);
2823 m_skipSavingOfUI
= skipSaving
;
2826 void MainWindow::resizeEvent(QResizeEvent
*)
2828 m_delayedStateSaving
->start();
2831 /** @short Make sure that the message gets loaded after the splitters have changed their position */
2832 void MainWindow::possiblyLoadMessageOnSplittersChanged()
2834 if (m_messageWidget
->isVisible() && !m_messageWidget
->size().isEmpty()) {
2835 // We do not have to check whether it's a different message; the setMessage() will do this or us
2836 // and there are multiple proxy models involved anyway
2837 QModelIndex index
= msgListWidget
->tree
->currentIndex();
2838 if (index
.isValid()) {
2839 // OTOH, setting an invalid QModelIndex would happily assert-fail
2840 m_messageWidget
->messageView
->setMessage(msgListWidget
->tree
->currentIndex());
2845 Imap::ImapAccess
*MainWindow::imapAccess() const
2847 return m_imapAccess
;
2850 void MainWindow::enableLoggingToDisk()
2852 protocolLogger
->slotSetPersistentLogging(true);
2855 void MainWindow::slotPluginsChanged()
2857 Plugins::AddressbookPlugin
*addressbook
= pluginManager()->addressbook();
2858 if (!addressbook
|| !(addressbook
->features() & Plugins::AddressbookPlugin::FeatureAddressbookWindow
))
2859 m_actionContactEditor
->setEnabled(false);
2861 m_actionContactEditor
->setEnabled(true);
2864 /** @short Update the default action to make sure that we show a correct status of the network connection */
2865 void MainWindow::updateNetworkIndication()
2867 if (QAction
*action
= qobject_cast
<QAction
*>(sender())) {
2868 if (action
->isChecked()) {
2869 m_netToolbarDefaultAction
->setIcon(action
->icon());
2874 void MainWindow::showStatusMessage(const QString
&message
)
2876 networkIndicator
->setToolTip(message
);
2877 if (isActiveWindow())
2878 QToolTip::showText(networkIndicator
->mapToGlobal(QPoint(0, 0)), message
);
2881 void MainWindow::slotMessageModelChanged(QAbstractItemModel
*model
)
2883 mailMimeTree
->setModel(model
);
2886 void MainWindow::slotFavoriteTagsChanged()
2888 for (int i
= 1; i
<= m_favoriteTags
->rowCount(); ++i
) {
2889 QAction
*action
= ShortcutHandler::instance()->action(QStringLiteral("action_tag_") + QString::number(i
));
2891 action
->setText(tr("Tag with \"%1\"").arg(m_favoriteTags
->tagNameByIndex(i
- 1)));
2895 void MainWindow::registerComposeWindow(ComposeWidget
* widget
)
2897 connect(widget
, &ComposeWidget::logged
, this, [this](const Common::LogKind kind
, const QString
& source
, const QString
& message
) {
2898 protocolLogger
->log(0, Common::LogMessage(QDateTime::currentDateTime(), kind
, source
, message
, 0));