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>
30 #include <QDockWidget>
31 #include <QFileDialog>
32 #include <QHeaderView>
33 #include <QInputDialog>
34 #include <QItemSelectionModel>
36 #include <QMessageBox>
37 #include <QProgressBar>
41 #include <QTextDocument>
43 #include <QToolButton>
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"
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 */
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
> >();
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()));
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);
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);
138 netExpensive
= new QAction(loadIcon(QLatin1String("network-expensive")), tr("Expensive Connection"), netPolicyGroup
);
139 netExpensive
->setCheckable(true);
141 netOnline
= new QAction(loadIcon(QLatin1String("network-online")), tr("Free Access"), netPolicyGroup
);
142 netOnline
->setCheckable(true);
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()));
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);
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
);
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
);
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());
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()));
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
);
527 QString cacheDir
= QDesktopServices::storageLocation(QDesktopServices::CacheLocation
);
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
)) {
543 QMessageBox::critical(this, tr("Cache Error"), tr("Failed to set safe permissions on cache directory %1").arg(cacheDir
));
544 shouldUsePersistentCache
= false;
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());
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());
563 if (s
.value(SettingsNames::cacheOfflineKey
).toString() == SettingsNames::cacheOfflineAll
) {
564 cache
->setRenewalThreshold(0);
567 int num
= s
.value(SettingsNames::cacheOfflineNumberDaysKey
, 30).toInt(&ok
);
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())
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
)
674 if (! index
.data(Imap::Mailbox::RoleMessageUid
).isValid())
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
)
690 if (! index
.data(Imap::Mailbox::RoleMessageUid
).isValid())
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())
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())
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
));
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);
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()));
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)
780 Imap::Mailbox::TreeItemMailbox
*mbox
= dynamic_cast<Imap::Mailbox::TreeItemMailbox
*>(
781 Imap::Mailbox::Model::realTreeItem(item
)
784 mbox
->rescanForChildMailboxes(model
);
788 /** @short Request a check for new messages in selected mailbox */
789 void MainWindow::slotResyncMbox()
791 if (! model
->isNetworkAvailable())
794 Q_FOREACH(const QModelIndex
&item
, mboxTree
->selectionModel()->selectedIndexes()) {
795 Q_ASSERT(item
.isValid());
796 if (item
.column() != 0)
798 Imap::Mailbox::TreeItemMailbox
*mbox
= dynamic_cast<Imap::Mailbox::TreeItemMailbox
*>(
799 Imap::Mailbox::Model::realTreeItem(item
)
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.
817 showImapLogger
->setChecked(true);
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
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
));
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
870 connectModelActions();
874 void MainWindow::authenticationRequested()
877 QString user
= s
.value(Common::SettingsNames::imapUserKey
).toString();
878 QString pass
= s
.value(Common::SettingsNames::imapPassKey
).toString();
879 if (m_ignoreStoredPassword
|| pass
.isEmpty()) {
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
);
886 model
->setImapUser(user
);
887 model
->setImapPassword(pass
);
889 model
->unsetImapPassword();
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
)
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);
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()) {
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
);
940 model
->setSslPolicy(certificateChain
, errors
, true);
942 model
->setSslPolicy(certificateChain
, errors
, false);
946 void MainWindow::nukeModels()
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();
959 mboxModel
->deleteLater();
961 prettyMboxModel
->deleteLater();
963 model
->deleteLater();
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)
979 if (!item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
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";
989 model
->markMessagesRead(translatedIndexes
, Imap::Mailbox::FLAG_ADD
);
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
);
1007 QModelIndex child
= current
.child(0, 0);
1008 if (child
.isValid()) {
1013 QModelIndex sibling
= current
.sibling(current
.row() + 1, 0);
1014 if (sibling
.isValid()) {
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
) {
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
);
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
;
1052 current
= current
.parent();
1054 if (!current
.isValid() && !wrapped
) {
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)
1070 if (!item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
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";
1080 model
->markMessagesDeleted(translatedIndexes
, Imap::Mailbox::FLAG_ADD
);
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);
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
) {
1119 parts
<< mboxPtr
->mailbox();
1120 parts
<< ui
.mailboxName
->text();
1121 if (ui
.otherMailboxes
->isChecked())
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())
1133 Imap::Mailbox::TreeItemMailbox
*mailbox
= dynamic_cast<Imap::Mailbox::TreeItemMailbox
*>(
1134 Imap::Mailbox::Model::realTreeItem(mboxTree
->currentIndex()));
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
);
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
);
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)
1213 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
1214 Imap::Message::MailAddress
addr(url
.queryItemValue(QLatin1String("X-Trojita-DisplayName")), QString(),
1218 Imap::Message::MailAddress
addr(q
.queryItemValue(QLatin1String("X-Trojita-DisplayName")), QString(),
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
)
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
);
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
)
1265 using namespace Imap
;
1266 QString message
= connectionStateToString(state
);
1267 enum { DURATION
= 10000 };
1268 bool transient
= false;
1271 case CONN_STATE_AUTHENTICATED
:
1272 case CONN_STATE_SELECTED
:
1276 // only the stuff above is transient
1279 statusBar()->showMessage(message
, transient
? DURATION
: 0);
1282 void MainWindow::slotShowLinkTarget(const QString
&link
)
1285 statusBar()->clearMessage();
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 © 2006-2012 Jan Kundrát <jkt@flaska.net> and "
1295 "<a href=\"http://quickgit.kde.org/?p=trojita.git&a=blob&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)
1312 if (! item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
1314 QModelIndex messageIndex
;
1315 Imap::Mailbox::TreeItemMessage
*message
= dynamic_cast<Imap::Mailbox::TreeItemMessage
*>(
1316 Imap::Mailbox::Model::realTreeItem(item
, 0, &messageIndex
)
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)
1356 if (! item
.data(Imap::Mailbox::RoleMessageUid
).isValid())
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()
1380 #ifdef XTUPLE_CONNECT
1381 void MainWindow::slotXtSyncCurrentMailbox()
1383 QModelIndex index
= mboxTree
->currentIndex();
1384 if (! index
.isValid())
1387 QString mailbox
= index
.data(Imap::Mailbox::RoleMailboxName
).toString();
1389 QStringList mailboxes
= s
.value(Common::SettingsNames::xtSyncMailboxList
).toStringList();
1390 if (xtIncludeMailboxInSync
->isChecked()) {
1391 if (! mailboxes
.contains(mailbox
)) {
1392 mailboxes
.append(mailbox
);
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
);
1403 void MainWindow::slotSubscribeCurrentMailbox()
1405 QModelIndex index
= mboxTree
->currentIndex();
1406 if (! index
.isValid())
1409 QString mailbox
= index
.data(Imap::Mailbox::RoleMailboxName
).toString();
1410 if (m_actionSubscribeMailbox
->isChecked()) {
1411 model
->subscribeMailbox(mailbox
);
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"...
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();
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.
1449 m_actionSortThreading
->setEnabled(true);
1450 if (!m_actionSortThreading
->isChecked())
1451 m_actionSortThreading
->trigger();
1452 m_actionSortNone
->setEnabled(false);
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);
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
);
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
;
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
;
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
)
1515 using namespace Imap::Mailbox
;
1519 case MsgListModel::SEEN
:
1520 case MsgListModel::COLUMN_COUNT
:
1521 case MsgListModel::BCC
:
1523 if (actionThreadMsgList
->isChecked())
1524 action
= m_actionSortThreading
;
1526 action
= m_actionSortNone
;
1528 case MsgListModel::SUBJECT
:
1529 action
= m_actionSortBySubject
;
1531 case MsgListModel::FROM
:
1532 action
= m_actionSortByFrom
;
1534 case MsgListModel::TO
:
1535 action
= m_actionSortByTo
;
1537 case MsgListModel::CC
:
1538 action
= m_actionSortByCc
;
1540 case MsgListModel::DATE
:
1541 action
= m_actionSortByDate
;
1543 case MsgListModel::RECEIVED_DATE
:
1544 action
= m_actionSortByArrival
;
1546 case MsgListModel::SIZE
:
1547 action
= m_actionSortBySize
;
1550 action
= m_actionSortNone
;
1553 action
->setChecked(true);
1554 if (order
== Qt::DescendingOrder
)
1555 m_actionSortDescending
->setChecked(true);
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);
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() &&
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();
1607 actionThreadMsgList
->setEnabled(false);
1610 void MainWindow::slotShowImapInfo()
1613 Q_FOREACH(const QString
&cap
, model
->capabilities()) {
1614 caps
+= tr("<li>%1</li>\n").arg(cap
);
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");
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");
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
);
1645 idString
+= tr("Server: %1").arg(serverName
);
1648 idString
+= tr(" (%1)").arg(vendor
);
1652 idString
+= tr(" on %1 %2").arg(os
, osVersion
);
1654 idString
+= tr(" on %1").arg(os
);
1656 idString
+= tr("</p>");
1658 idString
= tr("<p>The IMAP server did not return usable information about itself.</p>");
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());
1665 fullId
+= tr("<li>%1: %2</li>").arg(Qt::escape(QString::fromAscii(it
.key())), Qt::escape(QString::fromAscii(it
.value())));
1668 idString
+= tr("<ul>%1</ul>").arg(fullId
);
1670 idString
= tr("<p>The server has not provided information about its software version.</p>");
1673 QMessageBox::information(this, tr("IMAP Server Information"),
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()));
1693 setWindowTitle(trUtf8("%1 — Trojitá").arg(mailbox
.data(Imap::Mailbox::RoleShortMailboxName
).toString()));
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
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
;
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"));