docs++
[trojita.git] / src / Gui / Window.cpp
blobdfa8be163a1e518388e828331ba70da69517fcbe
1 /* Copyright (C) 2006 - 2012 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <QAuthenticator>
24 #include <QDesktopServices>
25 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
26 # include <QStandardPaths>
27 # include <QUrlQuery>
28 #endif
29 #include <QDir>
30 #include <QDockWidget>
31 #include <QFileDialog>
32 #include <QHeaderView>
33 #include <QInputDialog>
34 #include <QItemSelectionModel>
35 #include <QMenuBar>
36 #include <QMessageBox>
37 #include <QProgressBar>
38 #include <QSplitter>
39 #include <QSslError>
40 #include <QStatusBar>
41 #include <QTextDocument>
42 #include <QToolBar>
43 #include <QToolButton>
44 #include <QUrl>
46 #include "Common/PortNumbers.h"
47 #include "Common/SettingsNames.h"
48 #include "Composer/SenderIdentitiesModel.h"
49 #include "Imap/Model/CombinedCache.h"
50 #include "Imap/Model/MailboxModel.h"
51 #include "Imap/Model/MailboxTree.h"
52 #include "Imap/Model/MemoryCache.h"
53 #include "Imap/Model/Model.h"
54 #include "Imap/Model/ModelWatcher.h"
55 #include "Imap/Model/MsgListModel.h"
56 #include "Imap/Model/PrettyMailboxModel.h"
57 #include "Imap/Model/PrettyMsgListModel.h"
58 #include "Imap/Model/ThreadingMsgListModel.h"
59 #include "Imap/Model/Utils.h"
60 #include "Imap/Network/FileDownloadManager.h"
61 #include "AbookAddressbook.h"
62 #include "ComposeWidget.h"
63 #include "IconLoader.h"
64 #include "MailBoxTreeView.h"
65 #include "MessageListWidget.h"
66 #include "MessageView.h"
67 #include "MsgListView.h"
68 #include "ProtocolLoggerWidget.h"
69 #include "SettingsDialog.h"
70 #include "SimplePartWidget.h"
71 #include "Streams/SocketFactory.h"
72 #include "TaskProgressIndicator.h"
73 #include "Util.h"
74 #include "Window.h"
76 #include "ui_CreateMailboxDialog.h"
78 #include "Imap/Model/ModelTest/modeltest.h"
80 Q_DECLARE_METATYPE(QList<QSslCertificate>)
81 Q_DECLARE_METATYPE(QList<QSslError>)
83 /** @short All user-facing widgets and related classes */
84 namespace Gui
87 MainWindow::MainWindow(): QMainWindow(), model(0), m_actionSortNone(0), m_ignoreStoredPassword(false), m_supportsCatenate(false),
88 m_supportsGenUrlAuth(false), m_supportsImapSubmission(false)
90 qRegisterMetaType<QList<QSslCertificate> >();
91 qRegisterMetaType<QList<QSslError> >();
92 createWidgets();
94 migrateSettings();
95 QSettings s;
97 m_senderIdentities = new Composer::SenderIdentitiesModel(this);
98 m_senderIdentities->loadFromSettings(s);
100 if (! s.contains(Common::SettingsNames::imapMethodKey)) {
101 QTimer::singleShot(0, this, SLOT(slotShowSettings()));
105 setupModels();
106 createActions();
107 createMenus();
109 // Please note that Qt 4.6.1 really requires passing the method signature this way, *not* using the SLOT() macro
110 QDesktopServices::setUrlHandler(QLatin1String("mailto"), this, "slotComposeMailUrl");
112 slotUpdateWindowTitle();
115 void MainWindow::createActions()
117 m_mainToolbar = addToolBar(tr("Navigation"));
119 reloadMboxList = new QAction(style()->standardIcon(QStyle::SP_ArrowRight), tr("Update List of Child Mailboxes"), this);
120 connect(reloadMboxList, SIGNAL(triggered()), this, SLOT(slotReloadMboxList()));
122 resyncMbox = new QAction(loadIcon(QLatin1String("view-refresh")), tr("Check for New Messages"), this);
123 connect(resyncMbox, SIGNAL(triggered()), this, SLOT(slotResyncMbox()));
125 reloadAllMailboxes = new QAction(tr("Reload Everything"), this);
126 // connect later
128 exitAction = new QAction(loadIcon(QLatin1String("application-exit")), tr("E&xit"), this);
129 exitAction->setShortcut(tr("Ctrl+Q"));
130 exitAction->setStatusTip(tr("Exit the application"));
131 connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));
133 QActionGroup *netPolicyGroup = new QActionGroup(this);
134 netPolicyGroup->setExclusive(true);
135 netOffline = new QAction(loadIcon(QLatin1String("network-offline")), tr("Offline"), netPolicyGroup);
136 netOffline->setCheckable(true);
137 // connect later
138 netExpensive = new QAction(loadIcon(QLatin1String("network-expensive")), tr("Expensive Connection"), netPolicyGroup);
139 netExpensive->setCheckable(true);
140 // connect later
141 netOnline = new QAction(loadIcon(QLatin1String("network-online")), tr("Free Access"), netPolicyGroup);
142 netOnline->setCheckable(true);
143 // connect later
145 showFullView = new QAction(loadIcon(QLatin1String("edit-find-mail")), tr("Show Full Tree Window"), this);
146 showFullView->setCheckable(true);
147 connect(showFullView, SIGNAL(triggered(bool)), allDock, SLOT(setVisible(bool)));
148 connect(allDock, SIGNAL(visibilityChanged(bool)), showFullView, SLOT(setChecked(bool)));
150 showTaskView = new QAction(tr("Show ImapTask tree"), this);
151 showTaskView->setCheckable(true);
152 connect(showTaskView, SIGNAL(triggered(bool)), taskDock, SLOT(setVisible(bool)));
153 connect(taskDock, SIGNAL(visibilityChanged(bool)), showTaskView, SLOT(setChecked(bool)));
155 showImapLogger = new QAction(tr("Show IMAP protocol log"), this);
156 showImapLogger->setCheckable(true);
157 connect(showImapLogger, SIGNAL(toggled(bool)), imapLoggerDock, SLOT(setVisible(bool)));
158 connect(imapLoggerDock, SIGNAL(visibilityChanged(bool)), showImapLogger, SLOT(setChecked(bool)));
160 logPersistent = new QAction(tr("Log into %1").arg(Imap::Mailbox::persistentLogFileName()), this);
161 logPersistent->setCheckable(true);
162 connect(logPersistent, SIGNAL(triggered(bool)), imapLogger, SLOT(slotSetPersistentLogging(bool)));
164 showImapCapabilities = new QAction(tr("IMAP Server Information..."), this);
165 connect(showImapCapabilities, SIGNAL(triggered()), this, SLOT(slotShowImapInfo()));
167 showMenuBar = new QAction(loadIcon(QLatin1String("view-list-text")), tr("Show Main Menu Bar"), this);
168 showMenuBar->setCheckable(true);
169 showMenuBar->setChecked(true);
170 showMenuBar->setShortcut(tr("Ctrl+M"));
171 addAction(showMenuBar); // otherwise it won't work with hidden menu bar
172 connect(showMenuBar, SIGNAL(triggered(bool)), menuBar(), SLOT(setVisible(bool)));
174 showToolBar = new QAction(tr("Show Toolbar"), this);
175 showToolBar->setCheckable(true);
176 showToolBar->setChecked(true);
177 connect(showToolBar, SIGNAL(triggered(bool)), m_mainToolbar, SLOT(setVisible(bool)));
179 configSettings = new QAction(loadIcon(QLatin1String("configure")), tr("Settings..."), this);
180 connect(configSettings, SIGNAL(triggered()), this, SLOT(slotShowSettings()));
182 composeMail = new QAction(loadIcon(QLatin1String("document-edit")), tr("Compose Mail..."), this);
183 connect(composeMail, SIGNAL(triggered()), this, SLOT(slotComposeMail()));
185 expunge = new QAction(loadIcon(QLatin1String("trash-empty")), tr("Expunge Mailbox"), this);
186 expunge->setShortcut(tr("Ctrl+E"));
187 connect(expunge, SIGNAL(triggered()), this, SLOT(slotExpunge()));
189 markAsRead = new QAction(loadIcon(QLatin1String("mail-mark-read")), tr("Mark as Read"), this);
190 markAsRead->setCheckable(true);
191 markAsRead->setShortcut(Qt::Key_M);
192 msgListWidget->tree->addAction(markAsRead);
193 connect(markAsRead, SIGNAL(triggered(bool)), this, SLOT(handleMarkAsRead(bool)));
195 m_nextMessage = new QAction(tr("Next Unread Message"), this);
196 m_nextMessage->setShortcut(Qt::Key_N);
197 msgListWidget->tree->addAction(m_nextMessage);
198 msgView->addAction(m_nextMessage);
199 connect(m_nextMessage, SIGNAL(triggered()), this, SLOT(slotNextUnread()));
201 m_previousMessage = new QAction(tr("Previous Unread Message"), this);
202 m_previousMessage->setShortcut(Qt::Key_P);
203 msgListWidget->tree->addAction(m_previousMessage);
204 msgView->addAction(m_previousMessage);
205 connect(m_previousMessage, SIGNAL(triggered()), this, SLOT(slotPreviousUnread()));
207 markAsDeleted = new QAction(loadIcon(QLatin1String("list-remove")), tr("Mark as Deleted"), this);
208 markAsDeleted->setCheckable(true);
209 markAsDeleted->setShortcut(Qt::Key_Delete);
210 msgListWidget->tree->addAction(markAsDeleted);
211 connect(markAsDeleted, SIGNAL(triggered(bool)), this, SLOT(handleMarkAsDeleted(bool)));
213 saveWholeMessage = new QAction(loadIcon(QLatin1String("file-save")), tr("Save Message..."), this);
214 msgListWidget->tree->addAction(saveWholeMessage);
215 connect(saveWholeMessage, SIGNAL(triggered()), this, SLOT(slotSaveCurrentMessageBody()));
217 viewMsgHeaders = new QAction(tr("View Message Headers..."), this);
218 viewMsgHeaders->setShortcut(tr("Ctrl+U"));
219 msgListWidget->tree->addAction(viewMsgHeaders);
220 connect(viewMsgHeaders, SIGNAL(triggered()), this, SLOT(slotViewMsgHeaders()));
222 createChildMailbox = new QAction(tr("Create Child Mailbox..."), this);
223 connect(createChildMailbox, SIGNAL(triggered()), this, SLOT(slotCreateMailboxBelowCurrent()));
225 createTopMailbox = new QAction(tr("Create New Mailbox..."), this);
226 connect(createTopMailbox, SIGNAL(triggered()), this, SLOT(slotCreateTopMailbox()));
228 deleteCurrentMailbox = new QAction(tr("Delete Mailbox"), this);
229 connect(deleteCurrentMailbox, SIGNAL(triggered()), this, SLOT(slotDeleteCurrentMailbox()));
231 #ifdef XTUPLE_CONNECT
232 xtIncludeMailboxInSync = new QAction(tr("Synchronize with xTuple"), this);
233 xtIncludeMailboxInSync->setCheckable(true);
234 connect(xtIncludeMailboxInSync, SIGNAL(triggered()), this, SLOT(slotXtSyncCurrentMailbox()));
235 #endif
237 // FIXME: add proper shortcuts
238 // this is complicated a bit because there shall typically be one shortcut to lead to the "default thing"...
239 m_replyPrivate = new QAction(tr("Private Reply"), this);
240 m_replyPrivate->setEnabled(false);
241 connect(m_replyPrivate, SIGNAL(triggered()), this, SLOT(slotReplyTo()));
243 m_replyAll = new QAction(tr("Reply to All"), this);
244 m_replyAll->setEnabled(false);
245 connect(m_replyAll, SIGNAL(triggered()), this, SLOT(slotReplyAll()));
247 m_replyList = new QAction(tr("Reply to Mailing List"), this);
248 m_replyList->setEnabled(false);
249 connect(m_replyList, SIGNAL(triggered()), this, SLOT(slotReplyList()));
251 actionThreadMsgList = new QAction(tr("Show Messages in Threads"), this);
252 actionThreadMsgList->setCheckable(true);
253 // This action is enabled/disabled by model's capabilities
254 actionThreadMsgList->setEnabled(false);
255 if (QSettings().value(Common::SettingsNames::guiMsgListShowThreading).toBool()) {
256 actionThreadMsgList->setChecked(true);
257 // The actual threading will be performed only when model updates its capabilities
259 connect(actionThreadMsgList, SIGNAL(triggered(bool)), this, SLOT(slotThreadMsgList()));
261 QActionGroup *sortOrderGroup = new QActionGroup(this);
262 m_actionSortAscending = new QAction(tr("Ascending"), sortOrderGroup);
263 m_actionSortAscending->setCheckable(true);
264 m_actionSortAscending->setChecked(true);
265 m_actionSortDescending = new QAction(tr("Descending"), sortOrderGroup);
266 m_actionSortDescending->setCheckable(true);
267 connect(sortOrderGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotSortingPreferenceChanged()));
269 QActionGroup *sortColumnGroup = new QActionGroup(this);
270 m_actionSortNone = new QAction(tr("No sorting"), sortColumnGroup);
271 m_actionSortNone->setCheckable(true);
272 m_actionSortThreading = new QAction(tr("Sorted by Threading"), sortColumnGroup);
273 m_actionSortThreading->setCheckable(true);
274 m_actionSortByArrival = new QAction(tr("Arrival"), sortColumnGroup);
275 m_actionSortByArrival->setCheckable(true);
276 m_actionSortByCc = new QAction(tr("Cc (Carbon Copy)"), sortColumnGroup);
277 m_actionSortByCc->setCheckable(true);
278 m_actionSortByDate = new QAction(tr("Date from Message Headers"), sortColumnGroup);
279 m_actionSortByDate->setCheckable(true);
280 m_actionSortByFrom = new QAction(tr("From Address"), sortColumnGroup);
281 m_actionSortByFrom->setCheckable(true);
282 m_actionSortBySize = new QAction(tr("Size"), sortColumnGroup);
283 m_actionSortBySize->setCheckable(true);
284 m_actionSortBySubject = new QAction(tr("Subject"), sortColumnGroup);
285 m_actionSortBySubject->setCheckable(true);
286 m_actionSortByTo = new QAction(tr("To Address"), sortColumnGroup);
287 m_actionSortByTo->setCheckable(true);
288 connect(sortColumnGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotSortingPreferenceChanged()));
289 slotSortingConfirmed(-1, Qt::AscendingOrder);
291 actionHideRead = new QAction(tr("Hide Read Messages"), this);
292 actionHideRead->setCheckable(true);
293 addAction(actionHideRead);
294 if (QSettings().value(Common::SettingsNames::guiMsgListHideRead).toBool()) {
295 actionHideRead->setChecked(true);
296 prettyMsgListModel->setHideRead(true);
298 connect(actionHideRead, SIGNAL(triggered(bool)), this, SLOT(slotHideRead()));
300 QActionGroup *layoutGroup = new QActionGroup(this);
301 m_actionLayoutCompact = new QAction(tr("Compact"), layoutGroup);
302 m_actionLayoutCompact->setCheckable(true);
303 m_actionLayoutCompact->setChecked(true);
304 connect(m_actionLayoutCompact, SIGNAL(triggered()), this, SLOT(slotLayoutCompact()));
305 m_actionLayoutWide = new QAction(tr("Wide"), layoutGroup);
306 m_actionLayoutWide->setCheckable(true);
307 connect(m_actionLayoutWide, SIGNAL(triggered()), this, SLOT(slotLayoutWide()));
309 if (QSettings().value(Common::SettingsNames::guiMainWindowLayout) == Common::SettingsNames::guiMainWindowLayoutWide) {
310 m_actionLayoutWide->setChecked(true);
311 slotLayoutWide();
314 m_actionShowOnlySubscribed = new QAction(tr("Show Only Subscribed Folders"), this);
315 m_actionShowOnlySubscribed->setCheckable(true);
316 m_actionShowOnlySubscribed->setEnabled(false);
317 connect(m_actionShowOnlySubscribed, SIGNAL(toggled(bool)), this, SLOT(slotShowOnlySubscribed()));
318 m_actionSubscribeMailbox = new QAction(tr("Subscribed"), this);
319 m_actionSubscribeMailbox->setCheckable(true);
320 m_actionSubscribeMailbox->setEnabled(false);
321 connect(m_actionSubscribeMailbox, SIGNAL(triggered()), this, SLOT(slotSubscribeCurrentMailbox()));
323 aboutTrojita = new QAction(trUtf8("About Trojitá..."), this);
324 connect(aboutTrojita, SIGNAL(triggered()), this, SLOT(slotShowAboutTrojita()));
326 donateToTrojita = new QAction(tr("Donate to the project"), this);
327 connect(donateToTrojita, SIGNAL(triggered()), this, SLOT(slotDonateToTrojita()));
329 connectModelActions();
331 m_replyButton = new QToolButton(this);
332 m_replyButton->setPopupMode(QToolButton::MenuButtonPopup);
333 m_replyMenu = new QMenu(m_replyButton);
334 m_replyMenu->addAction(m_replyPrivate);
335 m_replyMenu->addAction(m_replyAll);
336 m_replyMenu->addAction(m_replyList);
337 m_replyButton->setMenu(m_replyMenu);
338 m_replyButton->setDefaultAction(m_replyPrivate);
340 m_mainToolbar->addAction(composeMail);
341 m_mainToolbar->addWidget(m_replyButton);
342 m_mainToolbar->addAction(expunge);
343 m_mainToolbar->addSeparator();
344 m_mainToolbar->addAction(markAsRead);
345 m_mainToolbar->addAction(markAsDeleted);
346 m_mainToolbar->addSeparator();
347 m_mainToolbar->addAction(showMenuBar);
348 m_mainToolbar->addAction(configSettings);
351 void MainWindow::connectModelActions()
353 connect(reloadAllMailboxes, SIGNAL(triggered()), model, SLOT(reloadMailboxList()));
354 connect(netOffline, SIGNAL(triggered()), model, SLOT(setNetworkOffline()));
355 connect(netExpensive, SIGNAL(triggered()), model, SLOT(setNetworkExpensive()));
356 connect(netOnline, SIGNAL(triggered()), model, SLOT(setNetworkOnline()));
359 void MainWindow::createMenus()
361 QMenu *imapMenu = menuBar()->addMenu(tr("IMAP"));
362 imapMenu->addAction(composeMail);
363 imapMenu->addAction(m_replyPrivate);
364 imapMenu->addAction(m_replyAll);
365 imapMenu->addAction(m_replyList);
366 imapMenu->addAction(expunge);
367 imapMenu->addSeparator()->setText(tr("Network Access"));
368 QMenu *netPolicyMenu = imapMenu->addMenu(tr("Network Access"));
369 netPolicyMenu->addAction(netOffline);
370 netPolicyMenu->addAction(netExpensive);
371 netPolicyMenu->addAction(netOnline);
372 QMenu *debugMenu = imapMenu->addMenu(tr("Debugging"));
373 debugMenu->addAction(showFullView);
374 debugMenu->addAction(showTaskView);
375 debugMenu->addAction(showImapLogger);
376 debugMenu->addAction(logPersistent);
377 debugMenu->addAction(showImapCapabilities);
378 imapMenu->addSeparator();
379 imapMenu->addAction(configSettings);
380 imapMenu->addSeparator();
381 imapMenu->addAction(exitAction);
383 QMenu *viewMenu = menuBar()->addMenu(tr("View"));
384 viewMenu->addAction(showMenuBar);
385 viewMenu->addAction(showToolBar);
386 QMenu *layoutMenu = viewMenu->addMenu(tr("Layout"));
387 layoutMenu->addAction(m_actionLayoutCompact);
388 layoutMenu->addAction(m_actionLayoutWide);
389 viewMenu->addSeparator();
390 viewMenu->addAction(m_previousMessage);
391 viewMenu->addAction(m_nextMessage);
392 viewMenu->addSeparator();
393 QMenu *sortMenu = viewMenu->addMenu(tr("Sorting"));
394 sortMenu->addAction(m_actionSortNone);
395 sortMenu->addAction(m_actionSortThreading);
396 sortMenu->addAction(m_actionSortByArrival);
397 sortMenu->addAction(m_actionSortByCc);
398 sortMenu->addAction(m_actionSortByDate);
399 sortMenu->addAction(m_actionSortByFrom);
400 sortMenu->addAction(m_actionSortBySize);
401 sortMenu->addAction(m_actionSortBySubject);
402 sortMenu->addAction(m_actionSortByTo);
403 sortMenu->addSeparator();
404 sortMenu->addAction(m_actionSortAscending);
405 sortMenu->addAction(m_actionSortDescending);
407 viewMenu->addAction(actionThreadMsgList);
408 viewMenu->addAction(actionHideRead);
409 viewMenu->addAction(m_actionShowOnlySubscribed);
411 QMenu *mailboxMenu = menuBar()->addMenu(tr("Mailbox"));
412 mailboxMenu->addAction(resyncMbox);
413 mailboxMenu->addSeparator();
414 mailboxMenu->addAction(reloadAllMailboxes);
416 QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
417 helpMenu->addAction(donateToTrojita);
418 helpMenu->addSeparator();
419 helpMenu->addAction(aboutTrojita);
421 networkIndicator->setMenu(netPolicyMenu);
422 networkIndicator->setDefaultAction(netOnline);
425 void MainWindow::createWidgets()
427 mboxTree = new MailBoxTreeView();
428 connect(mboxTree, SIGNAL(customContextMenuRequested(const QPoint &)),
429 this, SLOT(showContextMenuMboxTree(const QPoint &)));
431 msgListWidget = new MessageListWidget();
432 msgListWidget->tree->setContextMenuPolicy(Qt::CustomContextMenu);
433 msgListWidget->tree->setAlternatingRowColors(true);
435 connect(msgListWidget->tree, SIGNAL(customContextMenuRequested(const QPoint &)),
436 this, SLOT(showContextMenuMsgListTree(const QPoint &)));
437 connect(msgListWidget->tree, SIGNAL(activated(const QModelIndex &)), this, SLOT(msgListActivated(const QModelIndex &)));
438 connect(msgListWidget->tree, SIGNAL(clicked(const QModelIndex &)), this, SLOT(msgListClicked(const QModelIndex &)));
439 connect(msgListWidget->tree, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(msgListDoubleClicked(const QModelIndex &)));
440 connect(msgListWidget, SIGNAL(requestingSearch(QStringList)), this, SLOT(slotSearchRequested(QStringList)));
442 msgView = new MessageView();
443 area = new QScrollArea();
444 area->setWidget(msgView);
445 area->setWidgetResizable(true);
446 connect(msgView, SIGNAL(messageChanged()), this, SLOT(scrollMessageUp()));
447 connect(msgView, SIGNAL(messageChanged()), this, SLOT(slotUpdateMessageActions()));
448 connect(msgView, SIGNAL(linkHovered(QString)), this, SLOT(slotShowLinkTarget(QString)));
449 if (QSettings().value(Common::SettingsNames::appLoadHomepage, QVariant(true)).toBool() &&
450 !QSettings().value(Common::SettingsNames::imapStartOffline).toBool()) {
451 msgView->setHomepageUrl(QUrl(QString::fromUtf8("http://welcome.trojita.flaska.net/%1").arg(QCoreApplication::applicationVersion())));
454 m_mainHSplitter = new QSplitter();
455 m_mainVSplitter = new QSplitter();
456 m_mainVSplitter->setOrientation(Qt::Vertical);
457 m_mainVSplitter->addWidget(msgListWidget);
458 m_mainVSplitter->addWidget(area);
459 m_mainHSplitter->addWidget(mboxTree);
460 m_mainHSplitter->addWidget(m_mainVSplitter);
462 // The mboxTree shall not expand...
463 m_mainHSplitter->setStretchFactor(0, 0);
464 // ...while the msgListTree shall consume all the remaining space
465 m_mainHSplitter->setStretchFactor(1, 1);
467 setCentralWidget(m_mainHSplitter);
469 allDock = new QDockWidget("Everything", this);
470 allTree = new QTreeView(allDock);
471 allDock->hide();
472 allTree->setUniformRowHeights(true);
473 allTree->setHeaderHidden(true);
474 allDock->setWidget(allTree);
475 addDockWidget(Qt::LeftDockWidgetArea, allDock);
476 taskDock = new QDockWidget("IMAP Tasks", this);
477 taskTree = new QTreeView(taskDock);
478 taskDock->hide();
479 taskTree->setHeaderHidden(true);
480 taskDock->setWidget(taskTree);
481 addDockWidget(Qt::LeftDockWidgetArea, taskDock);
483 imapLoggerDock = new QDockWidget(tr("IMAP Protocol"), this);
484 imapLogger = new ProtocolLoggerWidget(imapLoggerDock);
485 imapLoggerDock->hide();
486 imapLoggerDock->setWidget(imapLogger);
487 addDockWidget(Qt::BottomDockWidgetArea, imapLoggerDock);
489 busyParsersIndicator = new TaskProgressIndicator(this);
490 statusBar()->addPermanentWidget(busyParsersIndicator);
491 busyParsersIndicator->hide();
493 networkIndicator = new QToolButton(this);
494 networkIndicator->setPopupMode(QToolButton::InstantPopup);
495 statusBar()->addPermanentWidget(networkIndicator);
498 void MainWindow::setupModels()
500 Imap::Mailbox::SocketFactoryPtr factory;
501 Imap::Mailbox::TaskFactoryPtr taskFactory(new Imap::Mailbox::TaskFactory());
502 QSettings s;
504 using Common::SettingsNames;
505 if (s.value(SettingsNames::imapMethodKey).toString() == SettingsNames::methodTCP) {
506 factory.reset(new Imap::Mailbox::TlsAbleSocketFactory(
507 s.value(SettingsNames::imapHostKey).toString(),
508 s.value(SettingsNames::imapPortKey, QString::number(Common::PORT_IMAP)).toUInt()));
509 factory->setStartTlsRequired(s.value(SettingsNames::imapStartTlsKey, true).toBool());
510 } else if (s.value(SettingsNames::imapMethodKey).toString() == SettingsNames::methodSSL) {
511 factory.reset(new Imap::Mailbox::SslSocketFactory(
512 s.value(SettingsNames::imapHostKey).toString(),
513 s.value(SettingsNames::imapPortKey, QString::number(Common::PORT_IMAPS)).toUInt()));
514 } else {
515 QStringList args = s.value(SettingsNames::imapProcessKey).toString().split(QLatin1Char(' '));
516 if (args.isEmpty()) {
517 // it's going to fail anyway
518 args << QLatin1String("");
520 QString appName = args.takeFirst();
521 factory.reset(new Imap::Mailbox::ProcessSocketFactory(appName, args));
524 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
525 QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
526 #else
527 QString cacheDir = QDesktopServices::storageLocation(QDesktopServices::CacheLocation);
528 #endif
529 if (cacheDir.isEmpty())
530 cacheDir = QDir::homePath() + QLatin1String("/.") + QCoreApplication::applicationName();
531 Imap::Mailbox::AbstractCache *cache = 0;
533 bool shouldUsePersistentCache = s.value(SettingsNames::cacheOfflineKey).toString() != SettingsNames::cacheOfflineNone;
534 if (shouldUsePersistentCache) {
535 if (! QDir().mkpath(cacheDir)) {
536 QMessageBox::critical(this, tr("Cache Error"), tr("Failed to create directory %1").arg(cacheDir));
537 shouldUsePersistentCache = false;
539 QFile::Permissions expectedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
540 if (QFileInfo(cacheDir).permissions() != expectedPerms) {
541 if (!QFile::setPermissions(cacheDir, expectedPerms)) {
542 #ifndef Q_OS_WIN32
543 QMessageBox::critical(this, tr("Cache Error"), tr("Failed to set safe permissions on cache directory %1").arg(cacheDir));
544 shouldUsePersistentCache = false;
545 #endif
550 //setProperty( "trojita-sqlcache-commit-period", QVariant(5000) );
551 //setProperty( "trojita-sqlcache-commit-delay", QVariant(1000) );
553 if (! shouldUsePersistentCache) {
554 cache = new Imap::Mailbox::MemoryCache(this, QString());
555 } else {
556 cache = new Imap::Mailbox::CombinedCache(this, QLatin1String("trojita-imap-cache"), cacheDir);
557 connect(cache, SIGNAL(error(QString)), this, SLOT(cacheError(QString)));
558 if (! static_cast<Imap::Mailbox::CombinedCache *>(cache)->open()) {
559 // Error message was already shown by the cacheError() slot
560 cache->deleteLater();
561 cache = new Imap::Mailbox::MemoryCache(this, QString());
562 } else {
563 if (s.value(SettingsNames::cacheOfflineKey).toString() == SettingsNames::cacheOfflineAll) {
564 cache->setRenewalThreshold(0);
565 } else {
566 bool ok;
567 int num = s.value(SettingsNames::cacheOfflineNumberDaysKey, 30).toInt(&ok);
568 if (!ok)
569 num = 30;
570 cache->setRenewalThreshold(num);
574 model = new Imap::Mailbox::Model(this, cache, factory, taskFactory, s.value(SettingsNames::imapStartOffline).toBool());
575 model->setObjectName(QLatin1String("model"));
576 model->setCapabilitiesBlacklist(s.value(SettingsNames::imapBlacklistedCapabilities).toStringList());
577 if (s.value(SettingsNames::imapEnableId, true).toBool()) {
578 model->setProperty("trojita-imap-enable-id", true);
580 mboxModel = new Imap::Mailbox::MailboxModel(this, model);
581 mboxModel->setObjectName(QLatin1String("mboxModel"));
582 prettyMboxModel = new Imap::Mailbox::PrettyMailboxModel(this, mboxModel);
583 prettyMboxModel->setObjectName(QLatin1String("prettyMboxModel"));
584 msgListModel = new Imap::Mailbox::MsgListModel(this, model);
585 msgListModel->setObjectName(QLatin1String("msgListModel"));
586 threadingMsgListModel = new Imap::Mailbox::ThreadingMsgListModel(this);
587 threadingMsgListModel->setObjectName(QLatin1String("threadingMsgListModel"));
588 threadingMsgListModel->setSourceModel(msgListModel);
589 prettyMsgListModel = new Imap::Mailbox::PrettyMsgListModel(this);
590 prettyMsgListModel->setSourceModel(threadingMsgListModel);
591 prettyMsgListModel->setObjectName(QLatin1String("prettyMsgListModel"));
593 connect(mboxTree, SIGNAL(clicked(const QModelIndex &)), msgListModel, SLOT(setMailbox(const QModelIndex &)));
594 connect(mboxTree, SIGNAL(activated(const QModelIndex &)), msgListModel, SLOT(setMailbox(const QModelIndex &)));
595 connect(msgListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateMessageFlags()));
596 connect(msgListModel, SIGNAL(messagesAvailable()), msgListWidget->tree, SLOT(scrollToBottom()));
597 connect(msgListModel, SIGNAL(rowsInserted(QModelIndex,int,int)), msgListWidget, SLOT(slotAutoEnableDisableSearch()));
598 connect(msgListModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), msgListWidget, SLOT(slotAutoEnableDisableSearch()));
599 connect(msgListModel, SIGNAL(layoutChanged()), msgListWidget, SLOT(slotAutoEnableDisableSearch()));
600 connect(msgListModel, SIGNAL(modelReset()), msgListWidget, SLOT(slotAutoEnableDisableSearch()));
602 connect(model, SIGNAL(alertReceived(const QString &)), this, SLOT(alertReceived(const QString &)));
603 connect(model, SIGNAL(connectionError(const QString &)), this, SLOT(connectionError(const QString &)));
604 connect(model, SIGNAL(authRequested()), this, SLOT(authenticationRequested()), Qt::QueuedConnection);
605 connect(model, SIGNAL(authAttemptFailed(QString)), this, SLOT(authenticationFailed(QString)));
606 connect(model, SIGNAL(needsSslDecision(QList<QSslCertificate>,QList<QSslError>)),
607 this, SLOT(sslErrors(QList<QSslCertificate>,QList<QSslError>)), Qt::QueuedConnection);
609 connect(model, SIGNAL(networkPolicyOffline()), this, SLOT(networkPolicyOffline()));
610 connect(model, SIGNAL(networkPolicyExpensive()), this, SLOT(networkPolicyExpensive()));
611 connect(model, SIGNAL(networkPolicyOnline()), this, SLOT(networkPolicyOnline()));
613 connect(model, SIGNAL(connectionStateChanged(QObject *,Imap::ConnectionState)),
614 this, SLOT(showConnectionStatus(QObject *,Imap::ConnectionState)));
616 connect(model, SIGNAL(mailboxDeletionFailed(QString,QString)), this, SLOT(slotMailboxDeleteFailed(QString,QString)));
617 connect(model, SIGNAL(mailboxCreationFailed(QString,QString)), this, SLOT(slotMailboxCreateFailed(QString,QString)));
619 connect(model, SIGNAL(logged(uint,Common::LogMessage)), imapLogger, SLOT(slotImapLogged(uint,Common::LogMessage)));
621 connect(model, SIGNAL(mailboxFirstUnseenMessage(QModelIndex,QModelIndex)), this, SLOT(slotScrollToUnseenMessage(QModelIndex,QModelIndex)));
623 connect(model, SIGNAL(capabilitiesUpdated(QStringList)), this, SLOT(slotCapabilitiesUpdated(QStringList)));
625 connect(msgListModel, SIGNAL(modelReset()), this, SLOT(slotUpdateWindowTitle()));
626 connect(model, SIGNAL(messageCountPossiblyChanged(QModelIndex)), this, SLOT(slotUpdateWindowTitle()));
628 connect(prettyMsgListModel, SIGNAL(sortingPreferenceChanged(int,Qt::SortOrder)), this, SLOT(slotSortingConfirmed(int,Qt::SortOrder)));
630 //Imap::Mailbox::ModelWatcher* w = new Imap::Mailbox::ModelWatcher( this );
631 //w->setModel( model );
633 //ModelTest* tester = new ModelTest( prettyMboxModel, this ); // when testing, test just one model at time
635 mboxTree->setModel(prettyMboxModel);
636 msgListWidget->tree->setModel(prettyMsgListModel);
637 connect(msgListWidget->tree->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
638 this, SLOT(msgListSelectionChanged(const QItemSelection &, const QItemSelection &)));
640 allTree->setModel(model);
641 taskTree->setModel(model->taskModel());
642 connect(model->taskModel(), SIGNAL(layoutChanged()), taskTree, SLOT(expandAll()));
643 connect(model->taskModel(), SIGNAL(modelReset()), taskTree, SLOT(expandAll()));
644 connect(model->taskModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), taskTree, SLOT(expandAll()));
645 connect(model->taskModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), taskTree, SLOT(expandAll()));
646 connect(model->taskModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), taskTree, SLOT(expandAll()));
648 busyParsersIndicator->setImapModel(model);
650 // TODO write more addressbook backends and make this configurable
651 m_addressBook = new AbookAddressbook();
654 void MainWindow::msgListSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
656 Q_UNUSED(deselected);
657 if (selected.indexes().isEmpty())
658 return;
660 QModelIndex index = selected.indexes().front();
661 if (index.data(Imap::Mailbox::RoleMessageUid).isValid()) {
662 updateMessageFlags(index);
663 msgView->setMessage(index);
667 void MainWindow::msgListActivated(const QModelIndex &index)
669 Q_ASSERT(index.isValid());
671 if (qApp->keyboardModifiers() & Qt::ShiftModifier || qApp->keyboardModifiers() & Qt::ControlModifier)
672 return;
674 if (! index.data(Imap::Mailbox::RoleMessageUid).isValid())
675 return;
677 if (index.column() != Imap::Mailbox::MsgListModel::SEEN) {
678 msgView->setMessage(index);
679 msgListWidget->tree->setCurrentIndex(index);
683 void MainWindow::msgListClicked(const QModelIndex &index)
685 Q_ASSERT(index.isValid());
687 if (qApp->keyboardModifiers() & Qt::ShiftModifier || qApp->keyboardModifiers() & Qt::ControlModifier)
688 return;
690 if (! index.data(Imap::Mailbox::RoleMessageUid).isValid())
691 return;
693 if (index.column() == Imap::Mailbox::MsgListModel::SEEN) {
694 QModelIndex translated;
695 Imap::Mailbox::Model::realTreeItem(index, 0, &translated);
696 if (!translated.data(Imap::Mailbox::RoleIsFetched).toBool())
697 return;
698 Imap::Mailbox::FlagsOperation flagOp = translated.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool() ?
699 Imap::Mailbox::FLAG_REMOVE : Imap::Mailbox::FLAG_ADD;
700 model->markMessagesRead(QModelIndexList() << translated, flagOp);
704 void MainWindow::msgListDoubleClicked(const QModelIndex &index)
706 Q_ASSERT(index.isValid());
708 if (! index.data(Imap::Mailbox::RoleMessageUid).isValid())
709 return;
711 MessageView *newView = new MessageView(0);
712 QModelIndex realIndex;
713 const Imap::Mailbox::Model *realModel;
714 Imap::Mailbox::TreeItemMessage *message = dynamic_cast<Imap::Mailbox::TreeItemMessage *>(
715 Imap::Mailbox::Model::realTreeItem(index, &realModel, &realIndex));
716 Q_ASSERT(message);
717 Q_ASSERT(realModel == model);
718 newView->setMessage(index);
720 QScrollArea *widget = new QScrollArea();
721 widget->setFocusPolicy(Qt::StrongFocus);
722 widget->setWidget(newView);
723 widget->setWidgetResizable(true);
724 widget->setWindowTitle(message->envelope(model).subject);
725 widget->setAttribute(Qt::WA_DeleteOnClose);
726 widget->resize(800, 600);
727 widget->show();
730 void MainWindow::showContextMenuMboxTree(const QPoint &position)
732 QList<QAction *> actionList;
733 if (mboxTree->indexAt(position).isValid()) {
734 actionList.append(createChildMailbox);
735 actionList.append(deleteCurrentMailbox);
736 actionList.append(resyncMbox);
737 actionList.append(reloadMboxList);
739 actionList.append(m_actionSubscribeMailbox);
740 m_actionSubscribeMailbox->setChecked(mboxTree->indexAt(position).data(Imap::Mailbox::RoleMailboxIsSubscribed).toBool());
742 #ifdef XTUPLE_CONNECT
743 actionList.append(xtIncludeMailboxInSync);
744 xtIncludeMailboxInSync->setChecked(
745 QSettings().value(Common::SettingsNames::xtSyncMailboxList).toStringList().contains(
746 mboxTree->indexAt(position).data(Imap::Mailbox::RoleMailboxName).toString()));
747 #endif
748 } else {
749 actionList.append(createTopMailbox);
751 actionList.append(reloadAllMailboxes);
752 actionList.append(m_actionShowOnlySubscribed);
753 QMenu::exec(actionList, mboxTree->mapToGlobal(position));
756 void MainWindow::showContextMenuMsgListTree(const QPoint &position)
758 QList<QAction *> actionList;
759 QModelIndex index = msgListWidget->tree->indexAt(position);
760 if (index.isValid()) {
761 updateMessageFlags(index);
762 actionList.append(markAsRead);
763 actionList.append(markAsDeleted);
764 actionList.append(saveWholeMessage);
765 actionList.append(viewMsgHeaders);
767 if (! actionList.isEmpty())
768 QMenu::exec(actionList, msgListWidget->tree->mapToGlobal(position));
771 /** @short Ask for an updated list of mailboxes situated below the selected one
774 void MainWindow::slotReloadMboxList()
776 Q_FOREACH(const QModelIndex &item, mboxTree->selectionModel()->selectedIndexes()) {
777 Q_ASSERT(item.isValid());
778 if (item.column() != 0)
779 continue;
780 Imap::Mailbox::TreeItemMailbox *mbox = dynamic_cast<Imap::Mailbox::TreeItemMailbox *>(
781 Imap::Mailbox::Model::realTreeItem(item)
783 Q_ASSERT(mbox);
784 mbox->rescanForChildMailboxes(model);
788 /** @short Request a check for new messages in selected mailbox */
789 void MainWindow::slotResyncMbox()
791 if (! model->isNetworkAvailable())
792 return;
794 Q_FOREACH(const QModelIndex &item, mboxTree->selectionModel()->selectedIndexes()) {
795 Q_ASSERT(item.isValid());
796 if (item.column() != 0)
797 continue;
798 Imap::Mailbox::TreeItemMailbox *mbox = dynamic_cast<Imap::Mailbox::TreeItemMailbox *>(
799 Imap::Mailbox::Model::realTreeItem(item)
801 Q_ASSERT(mbox);
802 model->resyncMailbox(item);
806 void MainWindow::alertReceived(const QString &message)
808 QMessageBox::warning(this, tr("IMAP Alert"), message);
811 void MainWindow::connectionError(const QString &message)
813 if (QSettings().contains(Common::SettingsNames::imapMethodKey)) {
814 QMessageBox::critical(this, tr("Connection Error"), message);
815 // Show the IMAP logger -- maybe some user will take that as a hint that they shall include it in the bug report.
816 // </joke>
817 showImapLogger->setChecked(true);
818 } else {
819 // hack: this slot is called even on the first run with no configuration
820 // We shouldn't have to worry about that, since the dialog is already scheduled for calling
821 // -> do nothing
825 void MainWindow::cacheError(const QString &message)
827 QMessageBox::critical(this, tr("IMAP Cache Error"),
828 tr("The caching subsystem managing a cache of the data already "
829 "downloaded from the IMAP server is having troubles. "
830 "All caching will be disabled.\n\n%1").arg(message));
831 if (model)
832 model->setCache(new Imap::Mailbox::MemoryCache(model, QString()));
835 void MainWindow::networkPolicyOffline()
837 netOffline->setChecked(true);
838 netExpensive->setChecked(false);
839 netOnline->setChecked(false);
840 updateActionsOnlineOffline(false);
841 networkIndicator->setDefaultAction(netOffline);
842 statusBar()->showMessage(tr("Offline"), 0);
845 void MainWindow::networkPolicyExpensive()
847 netOffline->setChecked(false);
848 netExpensive->setChecked(true);
849 netOnline->setChecked(false);
850 updateActionsOnlineOffline(true);
851 networkIndicator->setDefaultAction(netExpensive);
854 void MainWindow::networkPolicyOnline()
856 netOffline->setChecked(false);
857 netExpensive->setChecked(false);
858 netOnline->setChecked(true);
859 updateActionsOnlineOffline(true);
860 networkIndicator->setDefaultAction(netOnline);
863 void MainWindow::slotShowSettings()
865 SettingsDialog *dialog = new SettingsDialog(this, m_senderIdentities);
866 if (dialog->exec() == QDialog::Accepted) {
867 // FIXME: wipe cache in case we're moving between servers
868 nukeModels();
869 setupModels();
870 connectModelActions();
874 void MainWindow::authenticationRequested()
876 QSettings s;
877 QString user = s.value(Common::SettingsNames::imapUserKey).toString();
878 QString pass = s.value(Common::SettingsNames::imapPassKey).toString();
879 if (m_ignoreStoredPassword || pass.isEmpty()) {
880 bool ok;
881 pass = QInputDialog::getText(this, tr("IMAP Password"),
882 tr("Please provide password for %1 on %2:").arg(
883 user, QSettings().value(Common::SettingsNames::imapHostKey).toString()),
884 QLineEdit::Password, QString::null, &ok);
885 if (ok) {
886 model->setImapUser(user);
887 model->setImapPassword(pass);
888 } else {
889 model->unsetImapPassword();
891 } else {
892 model->setImapUser(user);
893 model->setImapPassword(pass);
897 void MainWindow::authenticationFailed(const QString &message)
899 m_ignoreStoredPassword = true;
900 QMessageBox::warning(this, tr("Login Failed"), message);
903 void MainWindow::sslErrors(const QList<QSslCertificate> &certificateChain, const QList<QSslError> &errors)
905 QSettings s;
906 QByteArray lastKnownCertPem = s.value(Common::SettingsNames::imapSslPemCertificate).toByteArray();
907 QList<QSslCertificate> lastKnownCerts = lastKnownCertPem.isEmpty() ?
908 QList<QSslCertificate>() :
909 QSslCertificate::fromData(lastKnownCertPem, QSsl::Pem);
910 if (!certificateChain.isEmpty()) {
911 if (!lastKnownCerts.isEmpty()) {
912 if (certificateChain == lastKnownCerts) {
913 // It's the same certificate as the last time; we should accept that
914 model->setSslPolicy(certificateChain, errors, true);
915 return;
920 QString message;
921 QString title;
922 Imap::Mailbox::CertificateUtils::IconType icon;
924 Imap::Mailbox::CertificateUtils::formatSslState(certificateChain, lastKnownCerts, lastKnownCertPem, errors,
925 &title, &message, &icon);
927 if (QMessageBox(static_cast<QMessageBox::Icon>(icon), title, message, QMessageBox::Yes | QMessageBox::No, this).exec() == QMessageBox::Yes) {
928 if (!certificateChain.isEmpty()) {
929 QByteArray buf;
930 Q_FOREACH(const QSslCertificate &cert, certificateChain) {
931 buf.append(cert.toPem());
933 s.setValue(Common::SettingsNames::imapSslPemCertificate, buf);
935 #ifdef XTUPLE_CONNECT
936 QSettings xtSettings(QSettings::UserScope, QString::fromAscii("xTuple.com"), QString::fromAscii("xTuple"));
937 xtSettings.setValue(Common::SettingsNames::imapSslPemCertificate, buf);
938 #endif
940 model->setSslPolicy(certificateChain, errors, true);
941 } else {
942 model->setSslPolicy(certificateChain, errors, false);
946 void MainWindow::nukeModels()
948 msgView->setEmpty();
949 mboxTree->setModel(0);
950 msgListWidget->tree->setModel(0);
951 allTree->setModel(0);
952 taskTree->setModel(0);
953 prettyMsgListModel->deleteLater();
954 prettyMsgListModel = 0;
955 threadingMsgListModel->deleteLater();
956 threadingMsgListModel = 0;
957 msgListModel->deleteLater();
958 msgListModel = 0;
959 mboxModel->deleteLater();
960 mboxModel = 0;
961 prettyMboxModel->deleteLater();
962 prettyMboxModel = 0;
963 model->deleteLater();
964 model = 0;
967 void MainWindow::slotComposeMail()
969 invokeComposeDialog();
972 void MainWindow::handleMarkAsRead(bool value)
974 QModelIndexList translatedIndexes;
975 Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
976 Q_ASSERT(item.isValid());
977 if (item.column() != 0)
978 continue;
979 if (!item.data(Imap::Mailbox::RoleMessageUid).isValid())
980 continue;
981 QModelIndex translated;
982 Imap::Mailbox::Model::realTreeItem(item, 0, &translated);
983 translatedIndexes << translated;
985 if (translatedIndexes.isEmpty()) {
986 qDebug() << "Model::handleMarkAsRead: no valid messages";
987 } else {
988 if (value)
989 model->markMessagesRead(translatedIndexes, Imap::Mailbox::FLAG_ADD);
990 else
991 model->markMessagesRead(translatedIndexes, Imap::Mailbox::FLAG_REMOVE);
995 void MainWindow::slotNextUnread()
997 QModelIndex current = msgListWidget->tree->currentIndex();
999 bool wrapped = false;
1000 while (current.isValid()) {
1001 if (!current.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool() && msgListWidget->tree->currentIndex() != current) {
1002 msgView->setMessage(current);
1003 msgListWidget->tree->setCurrentIndex(current);
1004 return;
1007 QModelIndex child = current.child(0, 0);
1008 if (child.isValid()) {
1009 current = child;
1010 continue;
1013 QModelIndex sibling = current.sibling(current.row() + 1, 0);
1014 if (sibling.isValid()) {
1015 current = sibling;
1016 continue;
1019 while (current.isValid() && msgListWidget->tree->model()->rowCount(current.parent()) - 1 == current.row()) {
1020 current = current.parent();
1022 current = current.sibling(current.row() + 1, 0);
1024 if (!current.isValid() && !wrapped) {
1025 wrapped = true;
1026 current = msgListWidget->tree->model()->index(0, 0);
1031 void MainWindow::slotPreviousUnread()
1033 QModelIndex current = msgListWidget->tree->currentIndex();
1035 bool wrapped = false;
1036 while (current.isValid()) {
1037 if (!current.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool() && msgListWidget->tree->currentIndex() != current) {
1038 msgView->setMessage(current);
1039 msgListWidget->tree->setCurrentIndex(current);
1040 return;
1043 QModelIndex candidate = current.sibling(current.row() - 1, 0);
1044 while (candidate.isValid() && current.model()->hasChildren(candidate)) {
1045 candidate = candidate.child(current.model()->rowCount(candidate) - 1, 0);
1046 Q_ASSERT(candidate.isValid());
1049 if (candidate.isValid()) {
1050 current = candidate;
1051 } else {
1052 current = current.parent();
1054 if (!current.isValid() && !wrapped) {
1055 wrapped = true;
1056 while (msgListWidget->tree->model()->hasChildren(current)) {
1057 current = msgListWidget->tree->model()->index(msgListWidget->tree->model()->rowCount(current) - 1, 0, current);
1063 void MainWindow::handleMarkAsDeleted(bool value)
1065 QModelIndexList translatedIndexes;
1066 Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
1067 Q_ASSERT(item.isValid());
1068 if (item.column() != 0)
1069 continue;
1070 if (!item.data(Imap::Mailbox::RoleMessageUid).isValid())
1071 continue;
1072 QModelIndex translated;
1073 Imap::Mailbox::Model::realTreeItem(item, 0, &translated);
1074 translatedIndexes << translated;
1076 if (translatedIndexes.isEmpty()) {
1077 qDebug() << "Model::handleMarkAsDeleted: no valid messages";
1078 } else {
1079 if (value)
1080 model->markMessagesDeleted(translatedIndexes, Imap::Mailbox::FLAG_ADD);
1081 else
1082 model->markMessagesDeleted(translatedIndexes, Imap::Mailbox::FLAG_REMOVE);
1086 void MainWindow::slotExpunge()
1088 model->expungeMailbox(msgListModel->currentMailbox());
1091 void MainWindow::slotCreateMailboxBelowCurrent()
1093 createMailboxBelow(mboxTree->currentIndex());
1096 void MainWindow::slotCreateTopMailbox()
1098 createMailboxBelow(QModelIndex());
1101 void MainWindow::createMailboxBelow(const QModelIndex &index)
1103 Imap::Mailbox::TreeItemMailbox *mboxPtr = index.isValid() ?
1104 dynamic_cast<Imap::Mailbox::TreeItemMailbox *>(
1105 Imap::Mailbox::Model::realTreeItem(index)) :
1108 Ui::CreateMailboxDialog ui;
1109 QDialog *dialog = new QDialog(this);
1110 ui.setupUi(dialog);
1112 dialog->setWindowTitle(mboxPtr ?
1113 tr("Create a Subfolder of %1").arg(mboxPtr->mailbox()) :
1114 tr("Create a Top-level Mailbox"));
1116 if (dialog->exec() == QDialog::Accepted) {
1117 QStringList parts;
1118 if (mboxPtr)
1119 parts << mboxPtr->mailbox();
1120 parts << ui.mailboxName->text();
1121 if (ui.otherMailboxes->isChecked())
1122 parts << QString();
1123 QString targetName = parts.join(mboxPtr ? mboxPtr->separator() : QString()); // FIXME: top-level separator
1124 model->createMailbox(targetName);
1128 void MainWindow::slotDeleteCurrentMailbox()
1130 if (! mboxTree->currentIndex().isValid())
1131 return;
1133 Imap::Mailbox::TreeItemMailbox *mailbox = dynamic_cast<Imap::Mailbox::TreeItemMailbox *>(
1134 Imap::Mailbox::Model::realTreeItem(mboxTree->currentIndex()));
1135 Q_ASSERT(mailbox);
1137 if (QMessageBox::question(this, tr("Delete Mailbox"),
1138 tr("Are you sure to delete mailbox %1?").arg(mailbox->mailbox()),
1139 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1140 model->deleteMailbox(mailbox->mailbox());
1144 void MainWindow::updateMessageFlags()
1146 updateMessageFlags(msgListWidget->tree->currentIndex());
1149 void MainWindow::updateMessageFlags(const QModelIndex &index)
1151 markAsRead->setChecked(index.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool());
1152 markAsDeleted->setChecked(index.data(Imap::Mailbox::RoleMessageIsMarkedDeleted).toBool());
1155 void MainWindow::updateActionsOnlineOffline(bool online)
1157 reloadMboxList->setEnabled(online);
1158 resyncMbox->setEnabled(online);
1159 expunge->setEnabled(online);
1160 createChildMailbox->setEnabled(online);
1161 createTopMailbox->setEnabled(online);
1162 deleteCurrentMailbox->setEnabled(online);
1163 markAsDeleted->setEnabled(online);
1164 markAsRead->setEnabled(online);
1165 showImapCapabilities->setEnabled(online);
1166 if (!online) {
1167 m_replyPrivate->setEnabled(false);
1168 m_replyAll->setEnabled(false);
1169 m_replyList->setEnabled(false);
1173 void MainWindow::slotUpdateMessageActions()
1175 Composer::RecipientList dummy;
1176 m_replyPrivate->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_PRIVATE, msgView->currentMessage(), dummy));
1177 m_replyAll->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_ALL, msgView->currentMessage(), dummy));
1178 m_replyList->setEnabled(Composer::Util::replyRecipientList(Composer::REPLY_LIST, msgView->currentMessage(), dummy));
1179 if (m_replyList->isEnabled()) {
1180 m_replyButton->setDefaultAction(m_replyList);
1181 } else {
1182 m_replyButton->setDefaultAction(m_replyPrivate);
1186 void MainWindow::scrollMessageUp()
1188 area->ensureVisible(0, 0, 0, 0);
1191 void MainWindow::slotReplyTo()
1193 msgView->reply(this, Composer::REPLY_PRIVATE);
1196 void MainWindow::slotReplyAll()
1198 msgView->reply(this, Composer::REPLY_ALL);
1201 void MainWindow::slotReplyList()
1203 msgView->reply(this, Composer::REPLY_LIST);
1206 void MainWindow::slotComposeMailUrl(const QUrl &url)
1208 Q_ASSERT(url.scheme().toLower() == QLatin1String("mailto"));
1210 QStringList list = url.path().split(QLatin1Char('@'));
1211 if (list.size() != 2)
1212 return;
1213 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
1214 Imap::Message::MailAddress addr(url.queryItemValue(QLatin1String("X-Trojita-DisplayName")), QString(),
1215 list[0], list[1]);
1216 #else
1217 QUrlQuery q(url);
1218 Imap::Message::MailAddress addr(q.queryItemValue(QLatin1String("X-Trojita-DisplayName")), QString(),
1219 list[0], list[1]);
1220 #endif
1221 RecipientsType recipients;
1222 recipients << qMakePair<Composer::RecipientKind,QString>(Composer::ADDRESS_TO, addr.asPrettyString());
1223 invokeComposeDialog(QString(), QString(), recipients);
1226 ComposeWidget *MainWindow::invokeComposeDialog(const QString &subject, const QString &body,
1227 const RecipientsType &recipients, const QList<QByteArray> &inReplyTo,
1228 const QList<QByteArray> &references, const QModelIndex &replyingToMessage)
1230 QSettings s;
1231 ComposeWidget *w = new ComposeWidget(this);
1233 // Trim the References header as per RFC 5537
1234 QList<QByteArray> trimmedReferences = references;
1235 int referencesSize = QByteArray("References: ").size();
1236 const int lineOverhead = 3; // one for the " " prefix, two for the \r\n suffix
1237 Q_FOREACH(const QByteArray &item, references)
1238 referencesSize += item.size() + lineOverhead;
1239 // The magic numbers are from RFC 5537
1240 while (referencesSize >= 998 && trimmedReferences.size() > 3) {
1241 referencesSize -= trimmedReferences.takeAt(1).size() + lineOverhead;
1244 w->setData(recipients, subject, body, inReplyTo, trimmedReferences, replyingToMessage);
1245 Util::centerWidgetOnScreen(w);
1246 w->show();
1247 return w;
1250 void MainWindow::slotMailboxDeleteFailed(const QString &mailbox, const QString &msg)
1252 QMessageBox::warning(this, tr("Can't delete mailbox"),
1253 tr("Deleting mailbox \"%1\" failed with the following message:\n%2").arg(mailbox, msg));
1256 void MainWindow::slotMailboxCreateFailed(const QString &mailbox, const QString &msg)
1258 QMessageBox::warning(this, tr("Can't create mailbox"),
1259 tr("Creating mailbox \"%1\" failed with the following message:\n%2").arg(mailbox, msg));
1262 void MainWindow::showConnectionStatus(QObject *parser, Imap::ConnectionState state)
1264 Q_UNUSED(parser);
1265 using namespace Imap;
1266 QString message = connectionStateToString(state);
1267 enum { DURATION = 10000 };
1268 bool transient = false;
1270 switch (state) {
1271 case CONN_STATE_AUTHENTICATED:
1272 case CONN_STATE_SELECTED:
1273 transient = true;
1274 break;
1275 default:
1276 // only the stuff above is transient
1277 break;
1279 statusBar()->showMessage(message, transient ? DURATION : 0);
1282 void MainWindow::slotShowLinkTarget(const QString &link)
1284 if (link.isEmpty())
1285 statusBar()->clearMessage();
1286 else
1287 statusBar()->showMessage(tr("Link target: %1").arg(link));
1290 void MainWindow::slotShowAboutTrojita()
1292 QMessageBox::about(this, trUtf8("About Trojitá"),
1293 trUtf8("<p>This is <b>Trojitá</b>, a fast Qt IMAP e-mail client</p>"
1294 "<p>Copyright &copy; 2006-2012 Jan Kundrát &lt;jkt@flaska.net&gt; and "
1295 "<a href=\"http://quickgit.kde.org/?p=trojita.git&amp;a=blob&amp;f=LICENSE\">others</a></p>"
1296 "<p>More information at <a href=\"http://trojita.flaska.net/\">http://trojita.flaska.net/</a></p>"
1297 "<p>You are using version %1.</p>").arg(
1298 QApplication::applicationVersion()));
1301 void MainWindow::slotDonateToTrojita()
1303 QDesktopServices::openUrl(QString(QLatin1String("http://sourceforge.net/donate/index.php?group_id=339456")));
1306 void MainWindow::slotSaveCurrentMessageBody()
1308 Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
1309 Q_ASSERT(item.isValid());
1310 if (item.column() != 0)
1311 continue;
1312 if (! item.data(Imap::Mailbox::RoleMessageUid).isValid())
1313 continue;
1314 QModelIndex messageIndex;
1315 Imap::Mailbox::TreeItemMessage *message = dynamic_cast<Imap::Mailbox::TreeItemMessage *>(
1316 Imap::Mailbox::Model::realTreeItem(item, 0, &messageIndex)
1318 Q_ASSERT(message);
1320 Imap::Network::MsgPartNetAccessManager *netAccess = new Imap::Network::MsgPartNetAccessManager(this);
1321 netAccess->setModelMessage(message->toIndex(model));
1322 Imap::Network::FileDownloadManager *fileDownloadManager =
1323 new Imap::Network::FileDownloadManager(this, netAccess, messageIndex.child(0, Imap::Mailbox::TreeItem::OFFSET_HEADER));
1324 // FIXME: change from "header" into "whole message"
1325 connect(fileDownloadManager, SIGNAL(succeeded()), fileDownloadManager, SLOT(deleteLater()));
1326 connect(fileDownloadManager, SIGNAL(transferError(QString)), fileDownloadManager, SLOT(deleteLater()));
1327 connect(fileDownloadManager, SIGNAL(fileNameRequested(QString *)),
1328 this, SLOT(slotDownloadMessageFileNameRequested(QString *)));
1329 connect(fileDownloadManager, SIGNAL(transferError(QString)),
1330 this, SLOT(slotDownloadMessageTransferError(QString)));
1331 connect(fileDownloadManager, SIGNAL(destroyed()), netAccess, SLOT(deleteLater()));
1332 fileDownloadManager->slotDownloadNow();
1336 void MainWindow::slotDownloadMessageTransferError(const QString &errorString)
1338 QMessageBox::critical(this, tr("Can't save message"),
1339 tr("Unable to save the attachment. Error:\n%1").arg(errorString));
1342 void MainWindow::slotDownloadMessageFileNameRequested(QString *fileName)
1344 *fileName = QFileDialog::getSaveFileName(this, tr("Save Message"),
1345 *fileName, QString(),
1346 0, QFileDialog::HideNameFilterDetails
1350 void MainWindow::slotViewMsgHeaders()
1352 Q_FOREACH(const QModelIndex &item, msgListWidget->tree->selectionModel()->selectedIndexes()) {
1353 Q_ASSERT(item.isValid());
1354 if (item.column() != 0)
1355 continue;
1356 if (! item.data(Imap::Mailbox::RoleMessageUid).isValid())
1357 continue;
1358 QModelIndex messageIndex;
1359 Imap::Mailbox::Model::realTreeItem(item, 0, &messageIndex);
1361 Imap::Network::MsgPartNetAccessManager *netAccess = new Imap::Network::MsgPartNetAccessManager(this);
1362 netAccess->setModelMessage(messageIndex);
1364 SimplePartWidget *headers = new SimplePartWidget(0, netAccess,
1365 messageIndex.model()->index(0, Imap::Mailbox::TreeItem::OFFSET_HEADER, messageIndex));
1366 headers->setAttribute(Qt::WA_DeleteOnClose);
1367 connect(headers, SIGNAL(destroyed()), netAccess, SLOT(deleteLater()));
1368 QAction *close = new QAction(loadIcon(QLatin1String("window-close")), tr("Close"), headers);
1369 headers->addAction(close);
1370 close->setShortcut(tr("Ctrl+W"));
1371 connect(close, SIGNAL(triggered()), headers, SLOT(close()));
1372 headers->setWindowTitle(tr("Message headers of UID %1 in %2").arg(
1373 QString::number(messageIndex.data(Imap::Mailbox::RoleMessageUid).toUInt()),
1374 messageIndex.parent().parent().data(Imap::Mailbox::RoleMailboxName).toString()
1376 headers->show();
1380 #ifdef XTUPLE_CONNECT
1381 void MainWindow::slotXtSyncCurrentMailbox()
1383 QModelIndex index = mboxTree->currentIndex();
1384 if (! index.isValid())
1385 return;
1387 QString mailbox = index.data(Imap::Mailbox::RoleMailboxName).toString();
1388 QSettings s;
1389 QStringList mailboxes = s.value(Common::SettingsNames::xtSyncMailboxList).toStringList();
1390 if (xtIncludeMailboxInSync->isChecked()) {
1391 if (! mailboxes.contains(mailbox)) {
1392 mailboxes.append(mailbox);
1394 } else {
1395 mailboxes.removeAll(mailbox);
1397 s.setValue(Common::SettingsNames::xtSyncMailboxList, mailboxes);
1398 QSettings(QSettings::UserScope, QString::fromAscii("xTuple.com"), QString::fromAscii("xTuple")).setValue(Common::SettingsNames::xtSyncMailboxList, mailboxes);
1399 prettyMboxModel->xtConnectStatusChanged(index);
1401 #endif
1403 void MainWindow::slotSubscribeCurrentMailbox()
1405 QModelIndex index = mboxTree->currentIndex();
1406 if (! index.isValid())
1407 return;
1409 QString mailbox = index.data(Imap::Mailbox::RoleMailboxName).toString();
1410 if (m_actionSubscribeMailbox->isChecked()) {
1411 model->subscribeMailbox(mailbox);
1412 } else {
1413 model->unsubscribeMailbox(mailbox);
1417 void MainWindow::slotShowOnlySubscribed()
1419 QSettings().setValue(Common::SettingsNames::guiMailboxListShowOnlySubscribed, m_actionShowOnlySubscribed->isChecked());
1420 if (m_actionShowOnlySubscribed->isEnabled()) {
1421 prettyMboxModel->setShowOnlySubscribed(m_actionShowOnlySubscribed->isChecked());
1425 void MainWindow::slotScrollToUnseenMessage(const QModelIndex &mailbox, const QModelIndex &message)
1427 // Now this is much, much more reliable than messing around with finding out an "interesting message"...
1428 Q_UNUSED(mailbox);
1429 Q_UNUSED(message);
1430 if (!m_actionSortNone->isChecked() && !m_actionSortThreading->isChecked()) {
1431 // we're using some funky sorting, better don't scroll anywhere
1433 if (m_actionSortDescending->isChecked()) {
1434 msgListWidget->tree->scrollToTop();
1435 } else {
1436 msgListWidget->tree->scrollToBottom();
1440 void MainWindow::slotThreadMsgList()
1442 // We want to save user's preferences and not override them with "threading disabled" when the server
1443 // doesn't report them, like in initial greetings. That's why we have to check for isEnabled() here.
1444 const bool useThreading = actionThreadMsgList->isChecked();
1446 // Switching betweeb threaded/unthreaded view shall resert the sorting criteria. The goal is to make
1447 // sorting rather seldomly used as people shall instead use proper threading.
1448 if (useThreading) {
1449 m_actionSortThreading->setEnabled(true);
1450 if (!m_actionSortThreading->isChecked())
1451 m_actionSortThreading->trigger();
1452 m_actionSortNone->setEnabled(false);
1453 } else {
1454 m_actionSortNone->setEnabled(true);
1455 if (!m_actionSortNone->isChecked())
1456 m_actionSortNone->trigger();
1457 m_actionSortThreading->setEnabled(false);
1460 QPersistentModelIndex currentItem = msgListWidget->tree->currentIndex();
1462 if (useThreading && actionThreadMsgList->isEnabled()) {
1463 msgListWidget->tree->setRootIsDecorated(true);
1464 threadingMsgListModel->setUserWantsThreading(true);
1465 } else {
1466 msgListWidget->tree->setRootIsDecorated(false);
1467 threadingMsgListModel->setUserWantsThreading(false);
1469 QSettings().setValue(Common::SettingsNames::guiMsgListShowThreading, QVariant(useThreading));
1471 if (currentItem.isValid()) {
1472 msgListWidget->tree->scrollTo(currentItem);
1473 } else {
1474 // If we cannot determine current item, at least scroll to a predictable place. Without this, the view
1475 // would jump to "weird" places, probably due to some heuristics about trying to show "roughly the same"
1476 // objects as what was visible before the reshuffling.
1477 msgListWidget->tree->scrollToBottom();
1481 void MainWindow::slotSortingPreferenceChanged()
1483 Qt::SortOrder order = m_actionSortDescending->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder;
1485 using namespace Imap::Mailbox;
1487 int column = -1;
1488 if (m_actionSortByArrival->isChecked()) {
1489 column = MsgListModel::RECEIVED_DATE;
1490 } else if (m_actionSortByCc->isChecked()) {
1491 column = MsgListModel::CC;
1492 } else if (m_actionSortByDate->isChecked()) {
1493 column = MsgListModel::DATE;
1494 } else if (m_actionSortByFrom->isChecked()) {
1495 column = MsgListModel::FROM;
1496 } else if (m_actionSortBySize->isChecked()) {
1497 column = MsgListModel::SIZE;
1498 } else if (m_actionSortBySubject->isChecked()) {
1499 column = MsgListModel::SUBJECT;
1500 } else if (m_actionSortByTo->isChecked()) {
1501 column = MsgListModel::TO;
1502 } else {
1503 column = -1;
1506 msgListWidget->tree->header()->setSortIndicator(column, order);
1509 void MainWindow::slotSortingConfirmed(int column, Qt::SortOrder order)
1511 // don't do anything during initialization
1512 if (!m_actionSortNone)
1513 return;
1515 using namespace Imap::Mailbox;
1516 QAction *action;
1518 switch (column) {
1519 case MsgListModel::SEEN:
1520 case MsgListModel::COLUMN_COUNT:
1521 case MsgListModel::BCC:
1522 case -1:
1523 if (actionThreadMsgList->isChecked())
1524 action = m_actionSortThreading;
1525 else
1526 action = m_actionSortNone;
1527 break;
1528 case MsgListModel::SUBJECT:
1529 action = m_actionSortBySubject;
1530 break;
1531 case MsgListModel::FROM:
1532 action = m_actionSortByFrom;
1533 break;
1534 case MsgListModel::TO:
1535 action = m_actionSortByTo;
1536 break;
1537 case MsgListModel::CC:
1538 action = m_actionSortByCc;
1539 break;
1540 case MsgListModel::DATE:
1541 action = m_actionSortByDate;
1542 break;
1543 case MsgListModel::RECEIVED_DATE:
1544 action = m_actionSortByArrival;
1545 break;
1546 case MsgListModel::SIZE:
1547 action = m_actionSortBySize;
1548 break;
1549 default:
1550 action = m_actionSortNone;
1553 action->setChecked(true);
1554 if (order == Qt::DescendingOrder)
1555 m_actionSortDescending->setChecked(true);
1556 else
1557 m_actionSortAscending->setChecked(true);
1560 void MainWindow::slotSearchRequested(const QStringList &searchConditions)
1562 if (!searchConditions.isEmpty() && actionThreadMsgList->isChecked()) {
1563 // right now, searching and threading doesn't play well together at all
1564 actionThreadMsgList->trigger();
1566 threadingMsgListModel->setUserSearchingSortingPreference(searchConditions, threadingMsgListModel->currentSortCriterium(),
1567 threadingMsgListModel->currentSortOrder());
1570 void MainWindow::slotHideRead()
1572 const bool hideRead = actionHideRead->isChecked();
1573 prettyMsgListModel->setHideRead(hideRead);
1574 QSettings().setValue(Common::SettingsNames::guiMsgListHideRead, QVariant(hideRead));
1577 void MainWindow::slotCapabilitiesUpdated(const QStringList &capabilities)
1579 if (capabilities.contains(QLatin1String("SORT"))) {
1580 m_actionSortByDate->actionGroup()->setEnabled(true);
1581 } else {
1582 m_actionSortByDate->actionGroup()->setEnabled(false);
1585 msgListWidget->setFuzzySearchSupported(capabilities.contains(QLatin1String("SEARCH=FUZZY")));
1587 m_actionShowOnlySubscribed->setEnabled(capabilities.contains(QLatin1String("LIST-EXTENDED")));
1588 m_actionShowOnlySubscribed->setChecked(m_actionShowOnlySubscribed->isEnabled() &&
1589 QSettings().value(
1590 Common::SettingsNames::guiMailboxListShowOnlySubscribed, false).toBool());
1591 m_actionSubscribeMailbox->setEnabled(m_actionShowOnlySubscribed->isEnabled());
1593 m_supportsCatenate = capabilities.contains(QLatin1String("CATENATE"));
1594 m_supportsGenUrlAuth = capabilities.contains(QLatin1String("URLAUTH"));
1595 m_supportsImapSubmission = capabilities.contains(QLatin1String("UIDPLUS")) &&
1596 capabilities.contains(QLatin1String("X-DRAFT-I01-SENDMAIL"));
1598 const QStringList supportedCapabilities = Imap::Mailbox::ThreadingMsgListModel::supportedCapabilities();
1599 Q_FOREACH(const QString &capability, capabilities) {
1600 if (supportedCapabilities.contains(capability)) {
1601 actionThreadMsgList->setEnabled(true);
1602 if (actionThreadMsgList->isChecked())
1603 slotThreadMsgList();
1604 return;
1607 actionThreadMsgList->setEnabled(false);
1610 void MainWindow::slotShowImapInfo()
1612 QString caps;
1613 Q_FOREACH(const QString &cap, model->capabilities()) {
1614 caps += tr("<li>%1</li>\n").arg(cap);
1617 QString idString;
1618 if (!model->serverId().isEmpty() && model->capabilities().contains(QLatin1String("ID"))) {
1619 QMap<QByteArray,QByteArray> serverId = model->serverId();
1621 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
1622 #define IMAP_ID_FIELD(Var, Name) bool has_##Var = serverId.contains(Name); \
1623 QString Var = has_##Var ? QString::fromUtf8(serverId[Name]).toHtmlEscaped() : tr("Unknown");
1624 #else
1625 #define IMAP_ID_FIELD(Var, Name) bool has_##Var = serverId.contains(Name); \
1626 QString Var = has_##Var ? Qt::escape(QString::fromAscii(serverId[Name])) : tr("Unknown");
1627 #endif
1628 IMAP_ID_FIELD(serverName, "name");
1629 IMAP_ID_FIELD(serverVersion, "version");
1630 IMAP_ID_FIELD(os, "os");
1631 IMAP_ID_FIELD(osVersion, "os-version");
1632 IMAP_ID_FIELD(vendor, "vendor");
1633 IMAP_ID_FIELD(supportUrl, "support-url");
1634 IMAP_ID_FIELD(address, "address");
1635 IMAP_ID_FIELD(date, "date");
1636 IMAP_ID_FIELD(command, "command");
1637 IMAP_ID_FIELD(arguments, "arguments");
1638 IMAP_ID_FIELD(environment, "environment");
1639 #undef IMAP_ID_FIELD
1640 if (has_serverName) {
1641 idString = tr("<p>");
1642 if (has_serverVersion)
1643 idString += tr("Server: %1 %2").arg(serverName, serverVersion);
1644 else
1645 idString += tr("Server: %1").arg(serverName);
1647 if (has_vendor) {
1648 idString += tr(" (%1)").arg(vendor);
1650 if (has_os) {
1651 if (has_osVersion)
1652 idString += tr(" on %1 %2").arg(os, osVersion);
1653 else
1654 idString += tr(" on %1").arg(os);
1656 idString += tr("</p>");
1657 } else {
1658 idString = tr("<p>The IMAP server did not return usable information about itself.</p>");
1660 QString fullId;
1661 for (QMap<QByteArray,QByteArray>::const_iterator it = serverId.constBegin(); it != serverId.constEnd(); ++it) {
1662 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
1663 fullId += tr("<li>%1: %2</li>").arg(QString::fromUtf8(it.key()).toHtmlEscaped(), QString::fromUtf8(it.value()).toHtmlEscaped());
1664 #else
1665 fullId += tr("<li>%1: %2</li>").arg(Qt::escape(QString::fromAscii(it.key())), Qt::escape(QString::fromAscii(it.value())));
1666 #endif
1668 idString += tr("<ul>%1</ul>").arg(fullId);
1669 } else {
1670 idString = tr("<p>The server has not provided information about its software version.</p>");
1673 QMessageBox::information(this, tr("IMAP Server Information"),
1674 tr("%1"
1675 "<p>The following capabilities are currently advertised:</p>\n"
1676 "<ul>\n%2</ul>").arg(idString, caps));
1679 QSize MainWindow::sizeHint() const
1681 return QSize(1150, 980);
1684 void MainWindow::slotUpdateWindowTitle()
1686 QModelIndex mailbox = msgListModel->currentMailbox();
1687 if (mailbox.isValid()) {
1688 if (mailbox.data(Imap::Mailbox::RoleUnreadMessageCount).toInt()) {
1689 setWindowTitle(trUtf8("%1 — %2 unread — Trojitá")
1690 .arg(mailbox.data(Imap::Mailbox::RoleShortMailboxName).toString(),
1691 mailbox.data(Imap::Mailbox::RoleUnreadMessageCount).toString()));
1692 } else {
1693 setWindowTitle(trUtf8("%1 — Trojitá").arg(mailbox.data(Imap::Mailbox::RoleShortMailboxName).toString()));
1695 } else {
1696 setWindowTitle(trUtf8("Trojitá"));
1700 void MainWindow::slotLayoutCompact()
1702 m_mainVSplitter->addWidget(area);
1703 QSettings().setValue(Common::SettingsNames::guiMainWindowLayout, Common::SettingsNames::guiMainWindowLayoutCompact);
1704 setMinimumWidth(800);
1707 void MainWindow::slotLayoutWide()
1709 m_mainHSplitter->addWidget(area);
1710 m_mainHSplitter->setStretchFactor(0, 0);
1711 m_mainHSplitter->setStretchFactor(1, 1);
1712 m_mainHSplitter->setStretchFactor(2, 1);
1713 QSettings().setValue(Common::SettingsNames::guiMainWindowLayout, Common::SettingsNames::guiMainWindowLayoutWide);
1714 setMinimumWidth(1250);
1717 Imap::Mailbox::Model *MainWindow::imapModel() const
1719 return model;
1722 bool MainWindow::isCatenateSupported() const
1724 return m_supportsCatenate;
1727 bool MainWindow::isGenUrlAuthSupported() const
1729 return m_supportsGenUrlAuth;
1732 bool MainWindow::isImapSubmissionSupported() const
1734 return m_supportsImapSubmission;
1737 /** @short Deal with various obsolete settings */
1738 void MainWindow::migrateSettings()
1740 using Common::SettingsNames;
1741 QSettings s;
1743 // Process the obsolete settings about the "cache backend". This has been changed to "offline stuff" after v0.3.
1744 if (s.value(SettingsNames::cacheMetadataKey).toString() == SettingsNames::cacheMetadataMemory) {
1745 s.setValue(SettingsNames::cacheOfflineKey, SettingsNames::cacheOfflineNone);
1746 s.remove(SettingsNames::cacheMetadataKey);
1748 // Also remove the older values used for cache lifetime management which were not used, but set to zero by default
1749 s.remove(QLatin1String("offline.sync"));
1750 s.remove(QLatin1String("offline.sync.days"));
1751 s.remove(QLatin1String("offline.sync.messages"));