/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Messaging Framework. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "emailclient.h" #include "selectfolder.h" #include "emailfoldermodel.h" #include "emailfolderview.h" #include "accountsettings.h" #include "searchview.h" #include "readmail.h" #include "writemail.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "statusmonitorwidget.h" #include "statusbar.h" #include "statusmonitor.h" #include #include static const unsigned int StatusBarHeight = 20; #ifdef LOAD_DEBUG_VERSION static const QString debugSuffix("d"); #else static const QString debugSuffix; #endif class ActivityIcon : public QLabel { Q_OBJECT public: ActivityIcon(QWidget* parent = Q_NULLPTR); private slots: void itemChanged(StatusItem* item); void showActivity(bool val); private: QMovie m_activeIcon; QPixmap m_inactiveIcon; }; ActivityIcon::ActivityIcon(QWidget* parent) : QLabel(parent), m_activeIcon(":icon/activity_working"), m_inactiveIcon(":icon/activity_idle") { setPixmap(m_inactiveIcon); setPalette(parent->palette()); connect(StatusMonitor::instance(),SIGNAL(added(StatusItem*)),this,SLOT(itemChanged(StatusItem*))); connect(StatusMonitor::instance(),SIGNAL(removed(StatusItem*)),this,SLOT(itemChanged(StatusItem*))); showActivity(StatusMonitor::instance()->itemCount() != 0); } void ActivityIcon::itemChanged(StatusItem* item) { Q_UNUSED(item); showActivity(StatusMonitor::instance()->itemCount() != 0); } void ActivityIcon::showActivity(bool val) { if (val) { if (m_activeIcon.state() == QMovie::Running) return; setMovie(&m_activeIcon); m_activeIcon.start(); } else { if (m_activeIcon.state() == QMovie::NotRunning) return; m_activeIcon.stop(); setPixmap(m_inactiveIcon); } } static const int defaultWidth = 1024; static const int defaultHeight = 768; enum ActivityType { Inactive = 0, Retrieving = 1, Sending = 2 }; static bool confirmDelete( QWidget *parent, const QString & caption, const QString & object ) { QString msg = "" + QObject::tr("Are you sure you want to delete: %1?").arg( object ) + ""; int r = QMessageBox::question( parent, caption, msg, QMessageBox::Yes, QMessageBox::No|QMessageBox::Default| QMessageBox::Escape, 0 ); return r == QMessageBox::Yes; } // This is used regularly: static const QMailMessage::MessageType nonEmailType = static_cast(QMailMessage::Mms | QMailMessage::Sms | QMailMessage::Instant | QMailMessage::System); class AcknowledgmentBox : public QMessageBox { Q_OBJECT public: static void show(const QString& title, const QString& text); private: AcknowledgmentBox(const QString& title, const QString& text); virtual void keyPressEvent(QKeyEvent* event); static const int _timeout = 3 * 1000; }; AcknowledgmentBox::AcknowledgmentBox(const QString& title, const QString& text) : QMessageBox(0) { setWindowTitle(title); setText(text); setIcon(QMessageBox::Information); setAttribute(Qt::WA_DeleteOnClose); QDialog::show(); QTimer::singleShot(_timeout, this, SLOT(accept())); } void AcknowledgmentBox::show(const QString& title, const QString& text) { (void)new AcknowledgmentBox(title, text); } void AcknowledgmentBox::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Select) { event->accept(); accept(); } else { QMessageBox::keyPressEvent(event); } } MessageUiBase::MessageUiBase(QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent,f), appTitle(tr("QtMail")), suspendMailCount(true), markingMode(false), threaded(true), selectionCount(0), emailCountSuspended(false) { setWindowTitle( appTitle ); } void MessageUiBase::viewSearchResults(const QMailMessageKey&, const QString& title) { //TODO QString caption(title); if (caption.isNull()) caption = tr("Search Results"); } void MessageUiBase::viewComposer() { writeMailWidget()->raise(); writeMailWidget()->activateWindow(); writeMailWidget()->show(); } WriteMail* MessageUiBase::writeMailWidget() const { static WriteMail* writeMail = const_cast(this)->createWriteMailWidget(); return writeMail; } ReadMail* MessageUiBase::readMailWidget() const { static ReadMail* readMail = const_cast(this)->createReadMailWidget(); return readMail; } EmailFolderView* MessageUiBase::folderView() const { static EmailFolderView* view = const_cast(this)->createFolderView(); return view; } MessageListView* MessageUiBase::messageListView() const { static MessageListView* view = const_cast(this)->createMessageListView(); return view; } EmailFolderModel* MessageUiBase::emailFolderModel() const { static EmailFolderModel* model = const_cast(this)->createEmailFolderModel(); return model; } SearchView* MessageUiBase::searchView() const { static SearchView* searchview = const_cast(this)->createSearchView(); return searchview; } void MessageUiBase::showFolderStatus(QMailMessageSet* item) { if (item) emit updateStatus(item->data(EmailFolderModel::FolderStatusDetailRole).value()); } void MessageUiBase::contextStatusUpdate() { if (suspendMailCount) return; showFolderStatus(folderView()->currentItem()); } void MessageUiBase::suspendMailCounts() { suspendMailCount = true; if (!emailFolderModel()->ignoreMailStoreUpdates()) { emailFolderModel()->setIgnoreMailStoreUpdates(true); emailCountSuspended = true; } } void MessageUiBase::resumeMailCounts() { suspendMailCount = false; if (emailCountSuspended) { emailFolderModel()->setIgnoreMailStoreUpdates(false); emailCountSuspended = false; } contextStatusUpdate(); } void MessageUiBase::messageSelectionChanged() { selectionCount = messageListView()->selected().count(); contextStatusUpdate(); } void MessageUiBase::setMarkingMode(bool set) { markingMode = set; messageListView()->setMarkingMode(markingMode); if (!markingMode) { // Clear whatever selections were previously made messageListView()->clearSelection(); } contextStatusUpdate(); } void MessageUiBase::setThreaded(bool set) { threaded = set; messageListView()->setThreaded(threaded); contextStatusUpdate(); } void MessageUiBase::clearStatusText() { emit clearStatus(); } void MessageUiBase::presentMessage(const QMailMessageId &id, QMailViewerFactory::PresentationType type) { readMailWidget()->displayMessage(id, type, false, false); } void MessageUiBase::updateWindowTitle() { QMailMessageSet* item = folderView()->currentItem(); if (!item) return; QString folderName = item->data(Qt::DisplayRole).value(); QString folderStatus = item->data(EmailFolderModel::FolderStatusRole).value(); QString accountName; QMailFolderId folderId = item->data(EmailFolderModel::FolderIdRole).value(); QMailAccountId accountId = item->data(EmailFolderModel::ContextualAccountIdRole).value(); if (!folderStatus.isEmpty()) folderStatus = " (" + folderStatus + ")"; //don't display account prefix for account root items bool isFolderItem = accountId.isValid() && folderId.isValid(); if (isFolderItem) { QMailAccount account(accountId); if (!account.name().isEmpty()) accountName = account.name() + '/'; } setWindowTitle(accountName + folderName + folderStatus + " - " + appTitle); } void MessageUiBase::checkUpdateWindowTitle(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (topLeft == folderView()->currentIndex() || bottomRight == folderView()->currentIndex()) updateWindowTitle(); } WriteMail* MessageUiBase::createWriteMailWidget() { WriteMail* writeMail = new WriteMail(this); writeMail->setObjectName("write-mail"); connect(writeMail, SIGNAL(enqueueMail(QMailMessage&)), this, SLOT(beginEnqueueMail(QMailMessage&))); connect(writeMail, SIGNAL(discardMail()), this, SLOT(discardMail())); connect(writeMail, SIGNAL(saveAsDraft(QMailMessage&)), this, SLOT(saveAsDraft(QMailMessage&))); connect(writeMail, SIGNAL(noSendAccount(QMailMessage::MessageType)), this, SLOT(noSendAccount(QMailMessage::MessageType))); connect(writeMail, SIGNAL(editAccounts()), this, SLOT(settings())); return writeMail; } ReadMail* MessageUiBase::createReadMailWidget() { ReadMail* readMail = new ReadMail(this); readMail->setObjectName("read-message"); readMail->setGeometry(geometry()); connect(readMail, SIGNAL(responseRequested(QMailMessage,QMailMessage::ResponseType)), this, SLOT(respond(QMailMessage,QMailMessage::ResponseType))); connect(readMail, SIGNAL(responseRequested(QMailMessagePart::Location,QMailMessage::ResponseType)), this, SLOT(respond(QMailMessagePart::Location,QMailMessage::ResponseType))); connect(readMail, SIGNAL(getMailRequested(QMailMessageMetaData)), this, SLOT(getSingleMail(QMailMessageMetaData))); connect(readMail, SIGNAL(readReplyRequested(QMailMessageMetaData)), this, SLOT(readReplyRequested(QMailMessageMetaData))); connect(readMail, SIGNAL(sendMessageTo(QMailAddress,QMailMessage::MessageType)), this, SLOT(sendMessageTo(QMailAddress,QMailMessage::MessageType))); connect(readMail, SIGNAL(viewMessage(QMailMessageId,QMailViewerFactory::PresentationType)), this, SLOT(presentMessage(QMailMessageId,QMailViewerFactory::PresentationType))); connect(readMail, SIGNAL(sendMessage(QMailMessage&)), this, SLOT(beginEnqueueMail(QMailMessage&))); connect(readMail, SIGNAL(retrieveMessagePortion(QMailMessageMetaData, uint)), this, SLOT(retrieveMessagePortion(QMailMessageMetaData, uint))); connect(readMail, SIGNAL(retrieveMessagePart(QMailMessagePart::Location)), this, SLOT(retrieveMessagePart(QMailMessagePart::Location))); connect(readMail, SIGNAL(retrieveMessagePartPortion(QMailMessagePart::Location, uint)), this, SLOT(retrieveMessagePartPortion(QMailMessagePart::Location, uint))); connect(readMail, SIGNAL(flagMessage(QMailMessageId, quint64, quint64)), this, SLOT(flagMessage(QMailMessageId, quint64, quint64))); return readMail; } EmailFolderView* MessageUiBase::createFolderView() { EmailFolderView* view = new EmailFolderView(this); view->setObjectName("read-email"); view->setModel(emailFolderModel()); connect(view, SIGNAL(selected(QMailMessageSet*)), this, SLOT(folderSelected(QMailMessageSet*))); connect(view, SIGNAL(selected(QMailMessageSet*)), this, SLOT(updateWindowTitle())); connect(emailFolderModel(),SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),this, SLOT(checkUpdateWindowTitle(const QModelIndex&,const QModelIndex&))); // connect(view, SIGNAL(selectionUpdated()), this, SLOT(updateWindowTitle())); return view; } MessageListView* MessageUiBase::createMessageListView() { MessageListView* view = new MessageListView(this); // Default sort is by descending send timestamp view->setSortKey(QMailMessageSortKey::timeStamp(Qt::DescendingOrder)); connect(view, SIGNAL(clicked(QMailMessageId)), this, SLOT(messageActivated())); connect(view, SIGNAL(selectionChanged()), this, SLOT(messageSelectionChanged())); connect(view, SIGNAL(rowCountChanged()), this, SLOT(messageSelectionChanged())); connect(view, SIGNAL(responseRequested(QMailMessage,QMailMessage::ResponseType)), this, SLOT(respond(QMailMessage,QMailMessage::ResponseType)) ); connect(view, SIGNAL(moreClicked()), this, SLOT(retrieveMoreMessages()) ); connect(view, SIGNAL(visibleMessagesChanged()), this, SLOT(retrieveVisibleMessagesFlags()) ); connect(view, SIGNAL(fullSearchRequested()),this,SLOT(search())); connect(view, SIGNAL(doubleClicked(QMailMessageId)), this, SLOT(messageOpenRequested())); return view; } EmailFolderModel* MessageUiBase::createEmailFolderModel() { EmailFolderModel* model = new EmailFolderModel(this); model->init(); return model; } SearchView* MessageUiBase::createSearchView() { SearchView* searchview = new SearchView(this); searchview->setObjectName("searchview"); return searchview; } EmailClient::EmailClient(QWidget *parent, Qt::WindowFlags f) : MessageUiBase( parent, f ), filesRead(false), transferStatus(Inactive), primaryActivity(Inactive), enableMessageActions(false), closeAfterTransmissions(false), closeAfterWrite(false), transmissionFailure(false), fetchTimer(this), autoGetMail(false), initialAction(None), preSearchWidgetId(-1), m_messageServerProcess(0), m_contextMenu(0), m_transmitAction(0), m_retrievalAction(0), m_flagRetrievalAction(0), m_exportAction(0) { setObjectName( "EmailClient" ); //start messageserver if it's not running if (!isMessageServerRunning() && !startMessageServer()) qFatal("Unable to start messageserver!"); //run account setup if we don't have any defined yet bool haveAccounts = QMailStore::instance()->countAccounts() > 0; if (!haveAccounts) QTimer::singleShot(0,this,SLOT(settings())); init(); setupUi(); } EmailClient::~EmailClient() { clearNewMessageStatus(messageListView()->key()); waitForMessageServer(); } void EmailClient::openFiles() { delayedInit(); if ( filesRead ) { if ( cachedDisplayMailId.isValid() ) displayCachedMail(); return; } filesRead = true; QMailMessageKey outboxFilter(QMailMessageKey::status(QMailMessage::Outbox)); if (QMailStore::instance()->countMessages(outboxFilter)) { // There are messages ready to be sent QTimer::singleShot( 0, this, SLOT(sendAllQueuedMail()) ); } if ( cachedDisplayMailId.isValid() ) { displayCachedMail(); } // See if there is a draft whose composition was interrupted by the Red Key (tm) QTimer::singleShot(0, this, SLOT(resumeInterruptedComposition())); } void EmailClient::displayCachedMail() { presentMessage(cachedDisplayMailId,QMailViewerFactory::AnyPresentation); cachedDisplayMailId = QMailMessageId(); } void EmailClient::resumeInterruptedComposition() { QSettings mailconf("QtProject", "qtmail"); mailconf.beginGroup("restart"); QVariant var = mailconf.value("lastDraftId"); if (!var.isNull()) { lastDraftId = QMailMessageId(var.toULongLong()); mailconf.remove("lastDraftId"); } mailconf.endGroup(); if (lastDraftId.isValid()) { if (QMessageBox::question(0, tr("Incomplete message"), tr("Messages was previously interrupted while composing a message.\n" "Do you want to resume composing the message?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { QMailMessage message(lastDraftId); modify(message); } } } bool EmailClient::startMessageServer() { qMailLog(Messaging) << "Starting messageserver child process..."; if (m_messageServerProcess) delete m_messageServerProcess; m_messageServerProcess = new QProcess(this); connect(m_messageServerProcess,SIGNAL(error(QProcess::ProcessError)), this,SLOT(messageServerProcessError(QProcess::ProcessError))); #ifdef Q_OS_WIN static const QString binary(QString("/messageserver5%1.exe").arg(debugSuffix)); #else static const QString binary(QString("/messageserver5%1").arg(debugSuffix)); #endif m_messageServerProcess->start(QMail::messageServerPath() + binary); return m_messageServerProcess->waitForStarted(); } bool EmailClient::waitForMessageServer() { if (m_messageServerProcess) { qMailLog(Messaging) << "Shutting down messageserver child process.."; bool result = m_messageServerProcess->waitForFinished(); delete m_messageServerProcess; m_messageServerProcess = 0; return result; } return true; } void EmailClient::messageServerProcessError(QProcess::ProcessError e) { QString errorMsg = tr("The Message server child process encountered an error (%1). Qtmail will now exit.").arg(static_cast(e)); QMessageBox::critical(this, tr("Message Server"), errorMsg); qFatal(errorMsg.toLatin1(), ""); } void EmailClient::connectServiceAction(QMailServiceAction* action) { connect(action, SIGNAL(connectivityChanged(QMailServiceAction::Connectivity)), this, SLOT(connectivityChanged(QMailServiceAction::Connectivity))); connect(action, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(activityChanged(QMailServiceAction::Activity))); connect(action, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status))); connect(action, SIGNAL(progressChanged(uint, uint)), this, SLOT(progressChanged(uint, uint))); } bool EmailClient::isMessageServerRunning() const { QString lockfile = "messageserver-instance.lock"; int lockid = QMail::fileLock(lockfile); if (lockid == -1) return true; QMail::fileUnlock(lockid); return false; } bool EmailClient::cleanExit(bool force) { bool result = true; if (isTransmitting()) { if (force) { qMailLog(Messaging) << "EmailClient::cleanExit: forcing cancel to exit"; cancelOperation(); //abort all transfer } result = false; } saveSettings(); return result; } bool EmailClient::closeImmediately() { if (isTransmitting()) { // obsolete? closeAfterTransmissionsFinished(); return false; } return true; } void EmailClient::setVisible(bool visible) { if (visible) { QPoint p(0, 0); const QScreen *scrn = QGuiApplication::primaryScreen(); int extraw = 0, extrah = 0; QRect desk; desk = scrn->availableGeometry(); QWidgetList list = QApplication::topLevelWidgets(); for (int i = 0; (extraw == 0 || extrah == 0) && i < list.size(); ++i) { QWidget * current = list.at(i); if (current->isVisible()) { int framew = current->geometry().x() - current->x(); int frameh = current->geometry().y() - current->y(); extraw = qMax(extraw, framew); extrah = qMax(extrah, frameh); } } // sanity check for decoration frames. With embedding, we // might get extraordinary values if (extraw == 0 || extrah == 0 || extraw >= 10 || extrah >= 40) { extrah = 40; extraw = 10; } p = QPoint(desk.x() + desk.width()/2, desk.y() + desk.height()/2); // p = origin of this p = QPoint(p.x()-width()/2 - extraw, p.y()-height()/2 - extrah); if (p.x() + extraw + width() > desk.x() + desk.width()) p.setX(desk.x() + desk.width() - width() - extraw); if (p.x() < desk.x()) p.setX(desk.x()); if (p.y() + extrah + height() > desk.y() + desk.height()) p.setY(desk.y() + desk.height() - height() - extrah); if (p.y() < desk.y()) p.setY(desk.y()); move(p); } QMainWindow::setVisible(visible); } void EmailClient::closeAfterTransmissionsFinished() { closeAfterWrite = false; closeAfterTransmissions = true; } void EmailClient::closeApplication() { cleanExit(false); // If we're still transmitting, just hide until it completes if (isTransmitting()) hide(); else close(); } void EmailClient::allWindowsClosed() { closeAfterTransmissionsFinished(); closeApplication(); } bool EmailClient::isTransmitting() { return (transferStatus != Inactive); } bool EmailClient::isSending() { return (transferStatus & Sending); } bool EmailClient::isRetrieving() { return (transferStatus & Retrieving); } void EmailClient::initActions() { if (!getMailButton) { getMailButton = new QAction( Qtmail::icon("sendandreceive"), tr("Synchronize"), this ); connect(getMailButton, SIGNAL(triggered()), this, SLOT(getAllNewMail()) ); getMailButton->setWhatsThis( tr("Synchronize all your accounts.") ); setActionVisible(getMailButton, false); getAccountButton = new QAction( Qtmail::icon("accountfolder"), QString(), this ); connect(getAccountButton, SIGNAL(triggered()), this, SLOT(getAccountMail()) ); getAccountButton->setWhatsThis( tr("Synchronize current account.") ); setActionVisible(getAccountButton, false); cancelButton = new QAction( Qtmail::icon("cancel"), tr("Cancel transfer"), this ); connect(cancelButton, SIGNAL(triggered()), this, SLOT(cancelOperation()) ); cancelButton->setWhatsThis( tr("Abort all transfer of mail.") ); setActionVisible(cancelButton, false); composeButton = new QAction( Qtmail::icon("compose"), tr("New"), this ); connect(composeButton, SIGNAL(triggered()), this, SLOT(composeActivated()) ); composeButton->setWhatsThis( tr("Write a new message.") ); searchButton = new QAction( Qtmail::icon("search"), tr("Search"), this ); connect(searchButton, SIGNAL(triggered()), this, SLOT(search()) ); searchButton->setWhatsThis( tr("Search for messages in your folders.") ); searchButton->setIconText(""); synchronizeAction = new QAction( this ); connect(synchronizeAction, SIGNAL(triggered()), this, SLOT(synchronizeFolder()) ); synchronizeAction->setWhatsThis( tr("Decide whether messages in this folder should be retrieved.") ); setActionVisible(synchronizeAction, false); createFolderAction = new QAction( tr("Create Folder"), this ); connect(createFolderAction, SIGNAL(triggered()), this, SLOT(createFolder())); createFolderAction->setWhatsThis( tr("Create folder and all messages and subfolders") ); setActionVisible(createFolderAction, false); deleteFolderAction = new QAction( tr("Delete Folder"), this ); connect(deleteFolderAction, SIGNAL(triggered()), this, SLOT(deleteFolder())); deleteFolderAction->setWhatsThis( tr("Delete folder and all messages and subfolders") ); setActionVisible(deleteFolderAction, false); renameFolderAction = new QAction( tr("Rename Folder"), this ); connect(renameFolderAction, SIGNAL(triggered()), this, SLOT(renameFolder())); renameFolderAction->setWhatsThis( tr("Give the folder a different name") ); setActionVisible(renameFolderAction, false); settingsAction = new QAction( Qtmail::icon("settings"), tr("Account settings..."), this ); connect(settingsAction, SIGNAL(triggered()), this, SLOT(settings())); settingsAction->setIconText(QString()); standardFoldersAction = new QAction( Qtmail::icon("Create standard folders"), tr("Create standard folders"), this ); connect(standardFoldersAction, SIGNAL(triggered()), this, SLOT(createStandardFolders())); standardFoldersAction->setIconText(QString()); workOfflineAction = new QAction( Qtmail::icon("workoffline"), tr("Work offline"), this ); connect(workOfflineAction, SIGNAL(triggered()), this, SLOT(connectionStateChanged())); workOfflineAction->setCheckable(true); workOfflineAction->setChecked(false); workOfflineAction->setIconText(QString()); notificationAction = new QAction(tr("Enable Notifications"), this); connect(notificationAction, SIGNAL(triggered()), this, SLOT(notificationStateChanged())); notificationAction->setCheckable(true); notificationAction->setChecked(false); notificationAction->setIconText(QString()); emptyTrashAction = new QAction( Qtmail::icon("trashfolder"), tr("Empty trash"), this ); connect(emptyTrashAction, SIGNAL(triggered()), this, SLOT(emptyTrashFolder())); setActionVisible(emptyTrashAction, false); moveAction = new QAction( this ); connect(moveAction, SIGNAL(triggered()), this, SLOT(moveSelectedMessages())); setActionVisible(moveAction, false); copyAction = new QAction( this ); connect(copyAction, SIGNAL(triggered()), this, SLOT(copySelectedMessages())); setActionVisible(copyAction, false); restoreAction = new QAction( this ); connect(restoreAction, SIGNAL(triggered()), this, SLOT(restoreSelectedMessages())); setActionVisible(restoreAction, false); selectAllAction = new QAction( tr("Select all"), this ); connect(selectAllAction, SIGNAL(triggered()), this, SLOT(selectAll())); setActionVisible(selectAllAction, false); deleteMailAction = new QAction( this ); deleteMailAction->setIcon( Qtmail::icon("deletemail") ); deleteMailAction->setShortcut(QKeySequence(Qt::Key_Delete)); connect(deleteMailAction, SIGNAL(triggered()), this, SLOT(deleteSelectedMessages())); setActionVisible(deleteMailAction, false); detachThreadAction = new QAction( tr("Detach from thread"), this ); connect(detachThreadAction, SIGNAL(triggered()), this, SLOT(detachThread())); setActionVisible(detachThreadAction, false); markAction = new QAction( tr("Mark messages"), this ); connect(markAction, SIGNAL(triggered()), this, SLOT(markMessages())); setActionVisible(markAction, true); threadAction = new QAction( tr("Unthread messages"), this ); connect(threadAction, SIGNAL(triggered()), this, SLOT(threadMessages())); setActionVisible(threadAction, true); replyAction= new QAction( Qtmail::icon("reply"), tr("Reply"), this ); connect(replyAction, SIGNAL(triggered()), this, SLOT(replyClicked())); replyAction->setWhatsThis( tr("Reply to sender only. Select Reply all from the menu if you want to reply to all recipients.") ); replyAllAction = new QAction( Qtmail::icon("replyall"), tr("Reply all"), this ); connect(replyAllAction, SIGNAL(triggered()), this, SLOT(replyAllClicked())); forwardAction = new QAction(Qtmail::icon("forward"),tr("Forward"), this ); connect(forwardAction, SIGNAL(triggered()), this, SLOT(forwardClicked())); nextMessageAction = new QAction( tr("Next Message"), this ); nextMessageAction->setShortcut(QKeySequence(Qt::ALT|Qt::Key_Right)); connect(nextMessageAction, SIGNAL(triggered()), this, SLOT(nextMessage())); previousMessageAction = new QAction( tr("Previous Message"), this ); previousMessageAction->setShortcut(QKeySequence(Qt::ALT|Qt::Key_Left)); connect(previousMessageAction, SIGNAL(triggered()), this, SLOT(previousMessage())); nextUnreadMessageAction = new QAction( tr("Next Unread Message"), this ); nextUnreadMessageAction->setShortcut(QKeySequence(Qt::ALT|Qt::Key_Plus)); connect(nextUnreadMessageAction, SIGNAL(triggered()), this, SLOT(nextUnreadMessage())); previousUnreadMessageAction = new QAction( tr("Previous Unread Message"), this ); previousUnreadMessageAction->setShortcut(QKeySequence(Qt::ALT|Qt::Key_Minus)); connect(previousUnreadMessageAction, SIGNAL(triggered()), this, SLOT(previousUnreadMessage())); scrollReaderDownAction = new QAction( tr("Scroll Down"), this ); scrollReaderDownAction->setShortcut(QKeySequence(Qt::ALT|Qt::Key_Down)); connect(scrollReaderDownAction, SIGNAL(triggered()), this, SLOT(scrollReaderDown())); scrollReaderUpAction = new QAction( tr("Scroll Up"), this ); scrollReaderUpAction->setShortcut(QKeySequence(Qt::ALT|Qt::Key_Up)); connect(scrollReaderUpAction, SIGNAL(triggered()), this, SLOT(scrollReaderUp())); readerMarkMessageAsUnreadAction = new QAction( tr("Mark as Unread"), this ); connect(readerMarkMessageAsUnreadAction, SIGNAL(triggered()), this, SLOT(readerMarkMessageAsUnread())); readerMarkMessageAsImportantAction = new QAction( tr("Mark as Important"), this ); connect(readerMarkMessageAsImportantAction, SIGNAL(triggered()), this, SLOT(readerMarkMessageAsImportant())); readerMarkMessageAsNotImportantAction = new QAction( tr("Mark as Not Important"), this ); connect(readerMarkMessageAsNotImportantAction, SIGNAL(triggered()), this, SLOT(readerMarkMessageAsNotImportant())); QMenu* fileMenu = m_contextMenu; fileMenu->addAction( composeButton ); fileMenu->addAction( getMailButton ); fileMenu->addAction( getAccountButton ); fileMenu->addAction( searchButton ); fileMenu->addAction( cancelButton ); fileMenu->addAction( emptyTrashAction ); fileMenu->addAction( settingsAction ); fileMenu->addAction(standardFoldersAction); fileMenu->addAction( workOfflineAction ); fileMenu->addAction( notificationAction ); fileMenu->addSeparator(); QAction* quitAction = fileMenu->addAction(Qtmail::icon("quit"),"Quit"); quitAction->setMenuRole(QAction::QuitRole); connect(quitAction,SIGNAL(triggered(bool)), this,SLOT(quit())); connect(fileMenu, SIGNAL(aboutToShow()), this, SLOT(updateActions())); QToolBar* toolBar = m_toolBar; m_toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBar->addAction( composeButton ); toolBar->addAction( getMailButton ); toolBar->addAction( cancelButton ); toolBar->addAction( searchButton ); toolBar->addSeparator(); toolBar->addAction( settingsAction ); toolBar->addSeparator(); toolBar->addAction(replyAction); toolBar->addAction(forwardAction); toolBar->addSeparator(); toolBar->addAction(deleteMailAction); updateGetMailButton(); folderView()->addAction(synchronizeAction); folderView()->addAction(createFolderAction); folderView()->addAction(deleteFolderAction); folderView()->addAction(renameFolderAction); folderView()->addAction(emptyTrashAction); folderView()->setContextMenuPolicy(Qt::ActionsContextMenu); messageListView()->addAction(replyAction); messageListView()->addAction(replyAllAction); messageListView()->addAction(forwardAction); messageListView()->addAction(createSeparator()); messageListView()->addAction( copyAction ); messageListView()->addAction( moveAction ); messageListView()->addAction( deleteMailAction ); messageListView()->addAction( restoreAction ); messageListView()->addAction(createSeparator()); messageListView()->addAction( selectAllAction ); messageListView()->addAction(createSeparator()); messageListView()->addAction( markAction ); messageListView()->addAction( threadAction ); messageListView()->addAction( detachThreadAction ); messageListView()->addAction(createSeparator()); messageListView()->addAction( previousMessageAction ); messageListView()->addAction( nextMessageAction ); messageListView()->addAction( previousUnreadMessageAction ); messageListView()->addAction( nextUnreadMessageAction ); messageListView()->setContextMenuPolicy(Qt::ActionsContextMenu); readMailWidget()->addAction(replyAction); readMailWidget()->addAction(replyAllAction); readMailWidget()->addAction(forwardAction); readMailWidget()->addAction(createSeparator()); readMailWidget()->addAction(deleteMailAction); readMailWidget()->addAction(createSeparator()); readMailWidget()->addAction(scrollReaderDownAction); readMailWidget()->addAction(scrollReaderUpAction); readMailWidget()->addAction(readerMarkMessageAsUnreadAction); readMailWidget()->addAction(readerMarkMessageAsImportantAction); readMailWidget()->addAction(readerMarkMessageAsNotImportantAction); } } void EmailClient::updateActions() { openFiles(); // Ensure that the actions have been initialised initActions(); //Enable marking and selectAll actions only if we have messages. int messageCount = messageListView()->rowCount(); setActionVisible(markAction, messageCount > 0); setActionVisible(selectAllAction, (messageCount > 1 && messageCount != selectionCount)); // Only enable empty trash action if the trash has messages in it QMailMessageKey typeFilter(QMailMessageKey::messageType(QMailMessage::Email)); QMailMessageKey trashFilter(QMailMessageKey::status(QMailMessage::Trash)); setActionVisible(threadAction, (messageCount > 0) && !markingMode); messageCount = QMailStore::instance()->countMessages(typeFilter & trashFilter); setActionVisible(emptyTrashAction, (messageCount > 0) && !markingMode); // Set the visibility for each action to whatever was last configured QMap::iterator it = actionVisibility.begin(), end = actionVisibility.end(); for ( ; it != end; ++it) it.key()->setVisible(it.value()); } void EmailClient::delayedInit() { if (moveAction) return; // delayedInit already done QMailStore* store = QMailStore::instance(); // Whenever these actions occur, we need to reload accounts that may have changed connect(store, SIGNAL(accountsAdded(QMailAccountIdList)), this, SLOT(accountsAdded(QMailAccountIdList))); connect(store, SIGNAL(accountsAdded(QMailAccountIdList)), this, SLOT(updateActions())); connect(store, SIGNAL(accountsRemoved(QMailAccountIdList)), this, SLOT(accountsRemoved(QMailAccountIdList))); connect(store, SIGNAL(accountsRemoved(QMailAccountIdList)), this, SLOT(updateActions())); connect(store, SIGNAL(accountsUpdated(QMailAccountIdList)), this, SLOT(accountsUpdated(QMailAccountIdList))); // We need to detect when messages are marked as deleted during downloading connect(store, SIGNAL(messagesUpdated(QMailMessageIdList)), this, SLOT(messagesUpdated(QMailMessageIdList))); connect(&fetchTimer, SIGNAL(timeout()), this, SLOT(automaticFetch()) ); // Ideally would make actions functions methods and delay their // creation until context menu is shown. initActions(); updateActions(); QTimer::singleShot(0, this, SLOT(openFiles()) ); } EmailFolderView* EmailClient::createFolderView() { EmailFolderView* view = MessageUiBase::createFolderView(); return view; } MessageListView* EmailClient::createMessageListView() { MessageListView* view = MessageUiBase::createMessageListView(); return view; } void EmailClient::init() { getMailButton = 0; getAccountButton = 0; cancelButton = 0; composeButton = 0; searchButton = 0; synchronizeAction = 0; settingsAction = 0; standardFoldersAction = 0; workOfflineAction = 0; emptyTrashAction = 0; moveAction = 0; copyAction = 0; restoreAction = 0; selectAllAction = 0; deleteMailAction = 0; m_exportAction = 0; // Connect our service action signals m_flagRetrievalAction = new QMailRetrievalAction(this); // Use a separate action for flag updates, which are not directed by the user connect(m_flagRetrievalAction, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(flagRetrievalActivityChanged(QMailServiceAction::Activity))); // We need to load the settings in case they affect our service handlers readSettings(); } void EmailClient::cancelOperation() { if ( !cancelButton->isEnabled() ) return; clearStatusText(); retrievalAccountIds.clear(); if (isSending()) { if (m_transmitAction->isRunning()) m_transmitAction->cancelOperation(); setSendingInProgress(false); } if (isRetrieving()) { if (m_retrievalAction->isRunning()) m_retrievalAction->cancelOperation(); setRetrievalInProgress(false); } if (m_flagRetrievalAction && m_flagRetrievalAction->isRunning()) m_flagRetrievalAction->cancelOperation(); if (m_exportAction && m_exportAction->isRunning()) m_exportAction->cancelOperation(); foreach(QMailStorageAction *action, m_outboxActions) { if (action->isRunning()) action->cancelOperation(); } } /* Enqueue mail must always store the mail in the outbox */ void EmailClient::beginEnqueueMail(QMailMessage& mail) { // Does this account support sending a message by reference from an external sent folder? QMailAccount account(mail.parentAccountId()); if ((account.status() & QMailAccount::CanReferenceExternalData) && (account.status() & QMailAccount::CanTransmitViaReference) && account.standardFolder(QMailFolder::SentFolder).isValid() && QMailFolder(account.standardFolder(QMailFolder::SentFolder)).id().isValid()) { mail.setStatus(QMailMessage::TransmitFromExternal, true); } mail.setStatus(QMailMessage::Outbox, true); m_outboxingMessages.append(mail); QMailStorageAction *outboxAction(new QMailStorageAction()); connect(outboxAction, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(finishEnqueueMail(QMailServiceAction::Activity))); m_outboxActions.append(outboxAction); if (!mail.id().isValid()) { // This message is present only on the local device until we externalise or send it mail.setStatus(QMailMessage::LocalOnly, true); outboxAction->addMessages(QMailMessageList() << mail); } else { outboxAction->updateMessages(QMailMessageList() << mail); } } void EmailClient::finishEnqueueMail(QMailServiceAction::Activity activity) { if ((activity == QMailServiceAction::Successful) || (activity == QMailServiceAction::Failed)) { QMailStorageAction *serviceAction(qobject_cast(sender())); if (serviceAction) { m_outboxActions.removeAll(serviceAction); serviceAction->deleteLater(); } } if (activity == QMailServiceAction::Successful) { if (workOfflineAction->isChecked()) { AcknowledgmentBox::show(tr("Message queued"), tr("Message has been queued in outbox")); } else { sendAllQueuedMail(true); } if (closeAfterWrite) { closeAfterTransmissionsFinished(); closeApplication(); } } else if (activity == QMailServiceAction::Failed) { QMailStore *store = QMailStore::instance(); foreach (QMailMessage mail, m_outboxingMessages) { if (!mail.id().isValid()) { mail.setStatus(QMailMessage::LocalOnly, true); store->addMessage(&mail); } else { store->updateMessage(&mail); } } m_outboxingMessages.clear(); AcknowledgmentBox::show(tr("Message queuing failure"), tr("Failed to queue message in outbox.")); } if (m_outboxActions.isEmpty()) { // No messages left to queue in outbox m_outboxingMessages.clear(); } } /* Simple, do nothing */ void EmailClient::discardMail() { // Reset these in case user chose reply but discarded message repliedFromMailId = QMailMessageId(); repliedFlags = 0; if (closeAfterWrite) { closeAfterTransmissionsFinished(); closeApplication(); } } void EmailClient::saveAsDraft(QMailMessage& mail) { // Mark the message as a draft so that it is presented correctly mail.setStatus(QMailMessage::Draft, true); bool inserted(false); if (!mail.id().isValid()) { // This message is present only on the local device until we externalise or send it mail.setStatus(QMailMessage::LocalOnly, true); inserted = QMailStore::instance()->addMessage(&mail); } else { QMailMessageId msgId = mail.id(); mail.setId(QMailMessageId()); mail.setStatus(QMailMessage::LocalOnly, true); mail.setServerUid(QString()); inserted = QMailStore::instance()->addMessage(&mail); QMailStore::instance()->removeMessage(msgId, QMailStore::CreateRemovalRecord); } if (inserted) { // Inform the responsible service that it is a draft QMailDisconnected::moveToStandardFolder(QMailMessageIdList() << mail.id(),QMailFolder::DraftsFolder); QMailDisconnected::flagMessage(mail.id(),QMailMessage::Draft,0,"Flagging message as draft"); exportPendingChanges(); lastDraftId = mail.id(); } else { QMailFolder folder(mail.parentFolderId()); accessError(folder.displayName()); } } /* Mark a message as replied/repliedall/forwarded */ void EmailClient::mailResponded() { if (repliedFromMailId.isValid()) { QMailDisconnected::flagMessage(repliedFromMailId,repliedFlags,0,"Marking message as replied/forwared"); exportPendingChanges(); repliedFromMailId = QMailMessageId(); repliedFlags = 0; } } // each message that belongs to the current found account void EmailClient::sendAllQueuedMail(bool userRequest) { transmissionFailure = false; QMailMessageKey outboxFilter(QMailMessageKey::status(QMailMessage::Outbox) & ~QMailMessageKey::status(QMailMessage::Trash)); if (transmitAccountIds.isEmpty()) { // Find which accounts have messages to transmit in the outbox for (const QMailMessageMetaData &metaData : QMailStore::instance()->messagesMetaData(outboxFilter, QMailMessageKey::ParentAccountId, QMailStore::ReturnDistinct)) { transmitAccountIds.append(metaData.parentAccountId()); } if (transmitAccountIds.isEmpty()) return; } if (userRequest) { // See if the message viewer wants to suppress the 'Sending messages' notification QMailMessageIdList outgoingIds = QMailStore::instance()->queryMessages(outboxFilter); if (!readMailWidget()->handleOutgoingMessages(outgoingIds)) { // Tell the user we're responding QString detail; if (outgoingIds.count() == 1) { QMailMessageMetaData mail(outgoingIds.first()); detail = mailType(mail.messageType()); } else { detail = tr("%n message(s)", "%1: number of messages", outgoingIds.count()); } AcknowledgmentBox::show(tr("Sending"), tr("Sending:") + ' ' + detail); } } while (!transmitAccountIds.isEmpty()) { QMailAccountId transmitId(transmitAccountIds.first()); transmitAccountIds.removeFirst(); if (verifyAccount(transmitId, true)) { setSendingInProgress(true); transmitAction("Sending messages")->transmitMessages(transmitId); return; } } } void EmailClient::rollBackUpdates(QMailAccountId accountId) { if (!QMailDisconnected::updatesOutstanding(accountId)) return; if (QMessageBox::Yes == QMessageBox::question(this, tr("Pending updates"), tr("There are local updates pending synchronization, " \ "do you want to revert these changes?"), QMessageBox::Yes | QMessageBox::No)) { QMailDisconnected::rollBackUpdates(accountId); } } void EmailClient::flagMessage(const QMailMessageId& id, quint64 setMask, quint64 unsetMask, const QString& description) { QMailDisconnected::flagMessage(id, setMask, unsetMask, description); exportPendingChanges(); } bool EmailClient::verifyAccount(const QMailAccountId &accountId, bool outgoing) { QMailAccount account(accountId); if ((outgoing && ((account.status() & QMailAccount::CanTransmit) == 0)) || (!outgoing && ((account.status() & QMailAccount::CanRetrieve) == 0))) { QString caption(outgoing ? tr("Cannot transmit") : tr("Cannot retrieve")); QString text(tr("Account configuration is incomplete.")); QMessageBox box(caption, text, QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton, QMessageBox::NoButton); box.exec(); return false; } return true; } void EmailClient::transmitCompleted() { // Check for messages that could nto be sent, e.g. due to bad recipients if (transmissionFailure) { transmissionFailure = false; const QMailServiceAction::Status status(m_transmitAction->status()); transferFailure(status.accountId, tr("Some messages could not be sent and have been left in the outbox. Verify that recipient addresses are well formed."), QMailServiceAction::Status::ErrInvalidAddress); } // If there are more SMTP accounts to service, continue if (!transmitAccountIds.isEmpty()) { sendAllQueuedMail(); } else { if (primaryActivity == Sending) clearStatusText(); setSendingInProgress(false); // If the sent message was a response, we have modified the original message's status mailResponded(); } } void EmailClient::retrievalCompleted() { messageListView()->updateActions(); // update GetMoreMessagesButton if (mailAccountId.isValid()) { // See if there are more accounts to process getNextNewMail(); } else { autoGetMail = false; if (primaryActivity == Retrieving) clearStatusText(); setRetrievalInProgress(false); } } void EmailClient::storageActionCompleted() { clearStatusText(); exportPendingChanges(); } void EmailClient::getNewMail() { // Try to preserve the message list selection selectedMessageId = messageListView()->current(); if (!selectedMessageId.isValid()) selectedMessageId = QMailMessageId(); setRetrievalInProgress(true); retrieveAction("Exporting account updates")->synchronize(mailAccountId, QMailRetrievalAction::defaultMinimum()); } void EmailClient::getAllNewMail() { if (isRetrieving()) { QString msg(tr("Cannot synchronize accounts because a synchronize operation is currently in progress")); QMessageBox::warning(0, tr("Synchronize in progress"), msg, tr("OK") ); return; } retrievalAccountIds.clear(); QMailAccountKey retrieveKey(QMailAccountKey::status(QMailAccount::CanRetrieve, QMailDataComparator::Includes)); QMailAccountKey enabledKey(QMailAccountKey::status(QMailAccount::Enabled, QMailDataComparator::Includes)); retrievalAccountIds = QMailStore::instance()->queryAccounts(retrieveKey & enabledKey); if (!retrievalAccountIds.isEmpty()) getNextNewMail(); } void EmailClient::getAccountMail() { if (isRetrieving()) { QString msg(tr("Cannot synchronize account because a synchronize operation is currently in progress")); QMessageBox::warning(0, tr("Synchronize in progress"), msg, tr("OK") ); return; } retrievalAccountIds.clear(); if (const QAction* action = static_cast(sender())) { QMailAccountId accountId(action->data().value()); retrievalAccountIds.append(accountId); getNextNewMail(); } } void EmailClient::getSingleMail(const QMailMessageMetaData& message) { if (isRetrieving()) { QString msg(tr("Cannot retrieve message because a retrieval operation is currently in progress")); QMessageBox::warning(0, tr("Retrieval in progress"), msg, tr("OK") ); return; } mailAccountId = QMailAccountId(); setRetrievalInProgress(true); retrieveAction("Retrieving single message")->retrieveMessages(QMailMessageIdList() << message.id(), QMailRetrievalAction::Content); } void EmailClient::retrieveMessagePortion(const QMailMessageMetaData& message, uint bytes) { if (isRetrieving()) { QString msg(tr("Cannot retrieve message portion because a retrieval operation is currently in progress")); QMessageBox::warning(0, tr("Retrieval in progress"), msg, tr("OK") ); return; } mailAccountId = QMailAccountId(); setRetrievalInProgress(true); QMailMessage msg(message.id()); retrieveAction("Retrieving message range")->retrieveMessageRange(message.id(), msg.body().length() + bytes); } void EmailClient::retrieveMessagePart(const QMailMessagePart::Location &partLocation) { if (isRetrieving()) { QString msg(tr("Cannot retrieve message part because a retrieval operation is currently in progress")); QMessageBox::warning(0, tr("Retrieval in progress"), msg, tr("OK") ); return; } if (!partLocation.isValid(true)) { QString msg(tr("Cannot retrieve message part without a valid message ID")); QMessageBox::warning(0, tr("Invalid part location"), msg, tr("OK") ); } else { QMailMessageMetaData metaData(partLocation.containingMessageId()); mailAccountId = QMailAccountId(); setRetrievalInProgress(true); retrieveAction("Retreiving message part")->retrieveMessagePart(partLocation); } } void EmailClient::retrieveMessagePartPortion(const QMailMessagePart::Location &partLocation, uint bytes) { if (isRetrieving()) { QString msg(tr("Cannot retrieve message part portion because a retrieval operation is currently in progress")); QMessageBox::warning(0, tr("Retrieval in progress"), msg, tr("OK") ); return; } if (!partLocation.isValid(true)) { QString msg(tr("Cannot retrieve message part without a valid message ID")); QMessageBox::warning(0, tr("Invalid part location"), msg, tr("OK") ); } else { QMailMessage messsage(partLocation.containingMessageId()); mailAccountId = QMailAccountId(); setRetrievalInProgress(true); const QMailMessagePart &part(messsage.partAt(partLocation)); retrieveAction("Retrieving message part portion")->retrieveMessagePartRange(partLocation, part.body().length() + bytes); } } void EmailClient::readReplyRequested(const QMailMessageMetaData& mail) { if (mail.messageType() == QMailMessage::Mms) { } } void EmailClient::getNextNewMail() { if (!retrievalAccountIds.isEmpty()) { mailAccountId = retrievalAccountIds.takeFirst(); getNewMail(); } else { // We have processed all accounts autoGetMail = false; mailAccountId = QMailAccountId(); if (primaryActivity == Retrieving) clearStatusText(); setRetrievalInProgress(false); retrieveVisibleMessagesFlags(); } } void EmailClient::sendFailure(const QMailAccountId &accountId) { setSendingInProgress(false); Q_UNUSED(accountId) } void EmailClient::receiveFailure(const QMailAccountId &accountId) { setRetrievalInProgress(false); autoGetMail = false; // Try the next account if we're working through a set of accounts if (!retrievalAccountIds.isEmpty()) QTimer::singleShot(0, this, SLOT(getNextNewMail())); Q_UNUSED(accountId) } void EmailClient::transferFailure(const QMailAccountId& accountId, const QString& text, int code) { QString caption, action; if (isSending()) { caption = tr("Send Failure"); action = tr("Error sending %1: %2", "%1: message type, %2: error text"); } else if (isRetrieving()) { caption = autoGetMail ? tr("Automatic Fetch Failure") : tr("Retrieve Failure"); action = tr("Error retrieving %1: %2", "%1: message type, %2: error text"); } if (!action.isEmpty()) { if (accountId.isValid()) { QMailAccount account(accountId); QMailMessage::MessageType type(account.messageType()); action = action.arg(mailType(type)).arg(text); // If we could have multiple accounts, name the relevant one if (type == QMailMessage::Email) action.prepend(" - ").prepend(account.name()); } else { action = action.arg(tr("message")).arg(text); } qMailLog(Messaging) << "transferFailure:" << caption << '-' << action; if (code != QMailServiceAction::Status::ErrCancel) { clearStatusText(); QMessageBox::warning(0, caption, action, QMessageBox::Ok); } else { emit updateStatus(tr("Transfer cancelled")); } rollBackUpdates(accountId); if (isSending()) { sendFailure(accountId); } else { receiveFailure(accountId); } } } void EmailClient::storageActionFailure(const QMailAccountId& accountId, const QString& text) { QString caption(tr("Storage Failure")); QString action(tr("Unable to perform requested action %1", "%1: error text")); if (accountId.isValid()) { QMailAccount account(accountId); action.prepend(" - ").prepend(account.name()); } clearStatusText(); QMessageBox::warning(0, caption, action.arg(text), QMessageBox::Ok); } QString EmailClient::mailType(QMailMessage::MessageType type) { QString key(QMailComposerFactory::defaultKey(type)); if (!key.isEmpty()) return QMailComposerFactory::displayName(key, type); return tr("Message"); } void EmailClient::messageActivated() { QMailMessageId currentId = messageListView()->current(); if (!currentId.isValid()) return; QMailMessage message(currentId); bool hasNext = false; bool hasPrevious = false; if (readMailWidget()->displayedMessage() != currentId) readMailWidget()->displayMessage(currentId, QMailViewerFactory::AnyPresentation, hasNext, hasPrevious); } void EmailClient::messageOpenRequested() { QMailMessageId currentId = messageListView()->current(); if (!currentId.isValid()) return; QMailMessage message(currentId); if (message.status() & QMailMessage::Draft) { modify(message); } } void EmailClient::showSearchResult(const QMailMessageId &id) { readMailWidget()->displayMessage(id, QMailViewerFactory::AnyPresentation, false, false); } void EmailClient::accessError(const QString &folderName) { QString msg = tr("Cannot access %1. Either there is insufficient space, or another program is accessing the mailbox.").arg(folderName); QMessageBox::critical( 0, tr("Access error"), msg ); } void EmailClient::readSettings() { QSettings mailconf("QtProject","qtmail"); mailconf.beginGroup("qtmailglobal"); mailconf.endGroup(); mailconf.beginGroup("settings"); int val = mailconf.value("interval", -1 ).toInt(); if ( val == -1 ) { fetchTimer.stop(); } else { fetchTimer.start( val * 60 * 1000); } mailconf.endGroup(); } bool EmailClient::saveSettings() { const int QTMAIL_CONFIG_VERSION = 100; QSettings mailconf("QtProject","qtmail"); mailconf.beginGroup("qtmailglobal"); mailconf.remove(""); mailconf.setValue("version", QTMAIL_CONFIG_VERSION ); mailconf.endGroup(); mailconf.beginGroup("qtmailglobal"); mailconf.endGroup(); return true; } void EmailClient::updateGetMailButton() { bool visible(false); // We can get only mail if we're currently inactive if (!isTransmitting()) { // At least one account must be able to retrieve mail QMailAccountKey retrieveKey(QMailAccountKey::status(QMailAccount::CanRetrieve, QMailDataComparator::Includes)); visible = (QMailStore::instance()->countAccounts(retrieveKey) != 0); } setActionVisible(getMailButton, visible); updateGetAccountButton(); } void EmailClient::updateGetAccountButton() { // We can get only mail if we're currently inactive bool inactive(!isTransmitting()); if (QMailMessageSet* item = folderView()->currentItem()) { QMailAccountId accountId(item->data(EmailFolderModel::ContextualAccountIdRole).value()); bool accountContext(accountId.isValid()); // Only show the get mail for account button if there are multiple accounts to retrieve from bool multipleMailAccounts = (emailAccounts().count() > 1); setActionVisible(getAccountButton, (inactive && accountContext && multipleMailAccounts)); } } void EmailClient::updateAccounts() { queuedAccountIds.clear(); updateGetMailButton(); } void EmailClient::deleteSelectedMessages() { QMailMessageIdList deleteList; deleteList = messageListView()->selected(); int deleteCount = deleteList.count(); if (deleteCount == 0) return; if (isSending()) { // Do not delete messages from the outbox folder while we're sending QMailMessageKey outboxFilter(QMailMessageKey::status(QMailMessage::Outbox)); if (QMailStore::instance()->countMessages(QMailMessageKey::id(deleteList) & outboxFilter)) { AcknowledgmentBox::show(tr("Cannot delete"), tr("Message transmission is in progress")); return; } } // If any of these messages are not yet trash, then we're only moving to trash QMailMessageKey idFilter(QMailMessageKey::id(deleteList)); QMailMessageKey notTrashFilter(QMailMessageKey::status(QMailMessage::Trash, QMailDataComparator::Excludes)); const bool deleting(QMailStore::instance()->countMessages(idFilter & notTrashFilter) == 0); // Tell the user we're doing what they asked for QString action; QString actionDetails; if ( deleting ) { QString item(tr("%n message(s)", "%1: number of messages", deleteCount)); if ( !confirmDelete( this, tr("Delete"), item ) ) return; action = tr("Deleting"); actionDetails = tr("Deleting %n message(s)", "%1: number of messages", deleteCount); } else { action = tr("Moving"); actionDetails = tr("Moving %n message(s) to Trash", "%1: number of messages", deleteCount); } AcknowledgmentBox::show(action, actionDetails); clearNewMessageStatus(QMailMessageKey::id(messageListView()->selected())); if (deleting) { //delete LocalOnly messages clientside first QMailMessageKey localOnlyKey(QMailMessageKey::id(deleteList) & QMailMessageKey::status(QMailMessage::LocalOnly)); QMailMessageIdList localOnlyIds(QMailStore::instance()->queryMessages(localOnlyKey)); if (!localOnlyIds.isEmpty()) { QMailStore::instance()->removeMessages(QMailMessageKey::id(localOnlyIds)); for (const QMailMessageId &id : localOnlyIds) { deleteList.removeAll(id); } } if (!deleteList.isEmpty()) storageAction("Deleting messages..")->deleteMessages(deleteList); } else { QMailDisconnected::moveToStandardFolder(deleteList,QMailFolder::TrashFolder); QMailDisconnected::flagMessages(deleteList,QMailMessage::Trash,0,"Marking messages as deleted"); exportPendingChanges(); } if (markingMode) { // After deleting the messages, clear marking mode setMarkingMode(false); } } void EmailClient::moveSelectedMessagesTo(const QMailFolderId &destination) { QMailMessageIdList moveList = messageListView()->selected(); if (moveList.isEmpty()) return; clearNewMessageStatus(QMailMessageKey::id(moveList)); QMailDisconnected::moveToFolder(moveList,destination); exportPendingChanges(); AcknowledgmentBox::show(tr("Moving"), tr("Moving %n message(s)", "%1: number of messages", moveList.count())); } void EmailClient::copySelectedMessagesTo(const QMailFolderId &destination) { QMailMessageIdList copyList = messageListView()->selected(); if (copyList.isEmpty()) return; clearNewMessageStatus(QMailMessageKey::id(copyList)); #if DISCONNECTED_COPY // experimental disconnected copy disabled for now. // retrieveMessageList and retriveMessages(flags) logic doesn't properly // handle copied messages copyToFolder(copyList,destination); #else storageAction("Copying messages")->onlineCopyMessages(copyList, destination); #endif AcknowledgmentBox::show(tr("Copying"), tr("Copying %n message(s)", "%1: number of messages", copyList.count())); } bool EmailClient::applyToSelectedFolder(void (EmailClient::*function)(const QMailFolderId&)) { locationSet.clear(); // Find the current locations for each of the selected messages QMailMessageKey key(QMailMessageKey::id(messageListView()->selected())); for (const QMailMessageMetaData &message : QMailStore::instance()->messagesMetaData(key, QMailMessageKey::ParentFolderId)) { locationSet.insert(message.parentFolderId()); } if (!locationSet.isEmpty()) { QSet locationAccountIds; foreach (const QMailFolderId &folderId, locationSet) { QMailFolder folder(folderId); if (folder.parentAccountId().isValid()) locationAccountIds.insert(folder.parentAccountId()); } QMailFolderIdList list; if (locationAccountIds.count() == 1) { AccountFolderModel model(*locationAccountIds.begin()); model.init(); QList invalidItems; invalidItems.append(model.itemFromIndex(model.indexFromAccountId(*locationAccountIds.begin()))); // If the message(s) are in a single location, do not permit that as a destination if (locationSet.count() == 1) { invalidItems.append(model.itemFromIndex(model.indexFromFolderId(*locationSet.begin()))); } SelectFolderDialog selectFolderDialog(&model); selectFolderDialog.setInvalidSelections(invalidItems); selectFolderDialog.exec(); if (selectFolderDialog.result() == QDialog::Accepted) { // Apply the function to the selected messages, with the selected folder argument (this->*function)(model.folderIdFromIndex(model.indexFromItem(selectFolderDialog.selectedItem()))); return true; } } else { // TODO: AcknowledgmentBox::show(tr("Whoops"), tr("Cannot handle messages from multiple accounts...")); } } return false; } void EmailClient::moveSelectedMessages() { QMailMessageIdList moveList = messageListView()->selected(); if (moveList.isEmpty()) return; if (isSending()) { // Do not move outbox messages while we're sending QMailMessageKey outboxFilter(QMailMessageKey::status(QMailMessage::Outbox)); if (QMailStore::instance()->countMessages(QMailMessageKey::id(moveList) & outboxFilter)) { AcknowledgmentBox::show(tr("Cannot move"), tr("Message transmission is in progress")); return; } } if (applyToSelectedFolder(&EmailClient::moveSelectedMessagesTo)) { if (markingMode) { // After moving the messages, clear marking mode setMarkingMode(false); } } } void EmailClient::copySelectedMessages() { QMailMessageIdList copyIds = messageListView()->selected(); #if DISCONNECTED_COPY // disabled for now foreach(QMailMessageId id, copyIds) { QMailMessage message(id); bool complete(message.status() & QMailMessage::ContentAvailable); for (uint i = 0; (i < message.partCount()) && complete; ++i) { complete &= message.partAt(i).contentAvailable(); } if (!complete) { // IMAP limitation AcknowledgmentBox::show(tr("Cannot copy"), tr("Can not copy partial message")); return; } } #endif if (applyToSelectedFolder(&EmailClient::copySelectedMessagesTo)) { if (markingMode) { // After copying the messages, clear marking mode setMarkingMode(false); } } } void EmailClient::restoreSelectedMessages() { QMailMessageKey selectedFilter(QMailMessageKey::id(messageListView()->selected())); QMailMessageKey trashFilter(QMailMessageKey::status(QMailMessage::Trash)); // Only messages currently in the trash folder should be restored QMailMessageIdList restoreIds = QMailStore::instance()->queryMessages(selectedFilter & trashFilter); if (restoreIds.isEmpty()) return; AcknowledgmentBox::show(tr("Restoring"), tr("Restoring %n message(s)", "%1: number of messages", restoreIds.count())); QMailDisconnected::restoreToPreviousFolder(QMailMessageKey::id(restoreIds)); QMailDisconnected::flagMessages(restoreIds,0,QMailMessage::Trash,"Restoring messages"); exportPendingChanges(); } void EmailClient::selectAll() { if (!markingMode) { // No point selecting messages unless we're in marking mode setMarkingMode(true); } messageListView()->selectAll(); } void EmailClient::emptyTrashFolder() { QMailMessageKey trashFilter(EmailStandardFolderMessageSet::contentKey(QMailFolder::TrashFolder)); QMailMessageIdList trashIds = QMailStore::instance()->queryMessages(trashFilter); if (trashIds.isEmpty()) return; if (confirmDelete(this, "Empty trash", tr("all messages in the trash"))) { AcknowledgmentBox::show(tr("Deleting"), tr("Deleting %n message(s)", "%1: number of messages", trashIds.count())); storageAction("Deleting messages")->deleteMessages(trashIds); } } void EmailClient::detachThread() { QMailMessageIdList ids(messageListView()->selected()); if (ids.count() == 1) { QString caption(tr("Detach")); QString msg(tr("Are you sure you want to detach this message from its current thread?")); if (QMessageBox::question(this, caption, msg, QMessageBox::Yes, QMessageBox::No|QMessageBox::Default|QMessageBox::Escape, 0) == QMessageBox::Yes) { QMailMessageMetaData metaData(ids.first()); metaData.setInResponseTo(QMailMessageId()); QMailStore::instance()->updateMessage(&metaData); } } } void EmailClient::connectivityChanged(QMailServiceAction::Connectivity /*connectivity*/) { } void EmailClient::activityChanged(QMailServiceAction::Activity activity) { if (QMailServiceAction *action = qobject_cast(sender())) { if (activity == QMailServiceAction::Successful) { if (action == m_transmitAction) { transmitCompleted(); } else if (action == m_retrievalAction) { retrievalCompleted(); } else if (action->metaObject()->className() == QString("QMailStorageAction")) { storageActionCompleted(); action->deleteLater(); } else if (action == m_exportAction) { m_queuedExports.takeFirst(); // finished successfully clearStatusText(); runNextPendingExport(); } } else if (activity == QMailServiceAction::Failed) { const QMailServiceAction::Status status(action->status()); if (action->metaObject()->className() == QString("QMailStorageAction")) { storageActionFailure(status.accountId, status.text); action->deleteLater(); } else if (action == m_exportAction) { rollBackUpdates(status.accountId); } else { transferFailure(status.accountId, status.text, status.errorCode); } } } } void EmailClient::statusChanged(const QMailServiceAction::Status &status) { if (QMailServiceAction *action = static_cast(sender())) { // If we have completed, don't show the status info if (action->activity() == QMailServiceAction::InProgress) { QString text = status.text; if (status.accountId.isValid()) { QMailAccount account(status.accountId); text.prepend(account.name() + " - "); } emit updateStatus(text); } } } void EmailClient::progressChanged(uint progress, uint total) { Q_UNUSED(progress); Q_UNUSED(total); // emit updateProgress(progress, total); } void EmailClient::messagesFailedTransmission() { transmissionFailure = true; } void EmailClient::flagRetrievalActivityChanged(QMailServiceAction::Activity activity) { if (QMailServiceAction *action = static_cast(sender())) { if (activity == QMailServiceAction::Failed) { // Report failure const QMailServiceAction::Status status(action->status()); qMailLog(Messaging) << "Failed to update message flags -" << status.text << "(" << status.errorCode << ")"; flagMessageIds.clear(); } else if (activity != QMailServiceAction::Successful) { return; } // Are there pending message IDS to be checked? if (!flagMessageIds.isEmpty()) { m_flagRetrievalAction->retrieveMessages(QList(flagMessageIds.begin(), flagMessageIds.end()), QMailRetrievalAction::Flags); flagMessageIds.clear(); } } } void EmailClient::folderSelected(QMailMessageSet *item) { if (item) { initActions(); contextStatusUpdate(); bool atFolder(false); bool showCreate(false); bool showDelete(false); bool showRename(false); QMailAccountId accountId(item->data(EmailFolderModel::ContextualAccountIdRole).value()); QMailFolderId folderId(item->data(EmailFolderModel::FolderIdRole).value()); if (accountId.isValid()) { selectedAccountId = accountId; QMailAccount account(accountId); getAccountButton->setText(tr("Synchronize %1", "%1:account name").arg(account.name())); getAccountButton->setData(accountId); // See if this is a folder that can have a menu if (folderId.isValid()) { atFolder = true; selectedFolderId = folderId; if (item->data(EmailFolderModel::FolderSynchronizationEnabledRole).value()) synchronizeAction->setText(tr("Exclude folder")); else synchronizeAction->setText(tr("Include folder")); if (item->data(EmailFolderModel::FolderChildCreationPermittedRole).value()) showCreate = true; if (item->data(EmailFolderModel::FolderDeletionPermittedRole).value()) showDelete = true; if (item->data(EmailFolderModel::FolderRenamePermittedRole).value()) showRename = true; } else { //Can still create a root folder selectedFolderId = QMailFolderId(0); //check if account supports creating folders showCreate = (account.status() & QMailAccount::CanCreateFolders); } } setActionVisible(synchronizeAction, atFolder); setActionVisible(createFolderAction, showCreate); setActionVisible(deleteFolderAction, showDelete); setActionVisible(renameFolderAction, showRename); updateGetAccountButton(); bool contentsChanged = (item->messageKey() != messageListView()->key()); if (contentsChanged) messageListView()->setKey(item->messageKey()); messageListView()->setFolderId(folderId); messageListView()->updateActions(); updateActions(); } } void EmailClient::deleteFolder() { QString folderName = QMailFolder(selectedFolderId).displayName(); if (QMessageBox::question(this, tr("Delete"), tr("Are you sure you wish to delete the folder %1 and all its contents?").arg(folderName), QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; storageAction("Deleting folder ")->onlineDeleteFolder(selectedFolderId); } void EmailClient::createFolder() { QString name = QInputDialog::getText(this, tr("New Folder Name"), tr("The name of the new folder should be: ")); if (name.isEmpty()) return; storageAction("Creating folder ")->onlineCreateFolder(name, selectedAccountId, selectedFolderId); } void EmailClient::renameFolder() { if (selectedFolderId.isValid()) { QString oldName = QMailFolder(selectedFolderId).displayName(); QString newName = QInputDialog::getText(this, tr("Rename Folder"), tr("Rename folder %1 to: ").arg(oldName)); if (newName.isEmpty()) return; storageAction("Renaming folder")->onlineRenameFolder(selectedFolderId, newName); } } void EmailClient::search() { static bool init = false; if (!init) { connect(searchView(), SIGNAL(searchResultSelected(QMailMessageId)), this, SLOT(showSearchResult(const QMailMessageId &))); init = true; } searchView()->raise(); searchView()->activateWindow(); searchView()->reset(); searchView()->show(); } void EmailClient::automaticFetch() { if (isRetrieving()) return; // TODO: remove this code - obsoleted by messageserver interval checking qWarning("get all new mail automatic"); autoGetMail = true; getAllNewMail(); } /* TODO: Is external edit still relevant? Someone external are making changes to the mailboxes. By this time we won't know what changes has been made (nor is it feasible to try to determine it). Close all actions which can have become invalid due to the external edit. A writemail window will as such close, but the information will be kept in memory (pasted when you reenter the writemail window (hopefully the external edit is done by then) */ void EmailClient::externalEdit(const QString &mailbox) { cancelOperation(); QString msg = mailbox + ' '; //no tr msg += tr("was edited externally"); emit updateStatus(msg); } bool EmailClient::checkMailConflict(const QString& msg1, const QString& msg2) { if ( writeMailWidget()->isVisible()) { QString message = tr("You are currently editing a message:
%1
").arg(msg1); switch ( QMessageBox::warning( 0, tr("Messages conflict"), message, tr("Yes"), tr("No"), 0, 0, 1 ) ) { case 0: { if ( !writeMailWidget()->saveChangesOnRequest() ) { QMessageBox::warning(0, tr("Autosave failed"), tr("Autosave failed:
%1
").arg(msg2)); return true; } break; } case 1: break; } } return false; } void EmailClient::replyClicked() { QMailMessageId currentId = readMailWidget()->displayedMessage(); if (currentId.isValid()) respond(QMailMessage(currentId),QMailMessage::Reply); } void EmailClient::replyAllClicked() { QMailMessageId currentId = readMailWidget()->displayedMessage(); if (currentId.isValid()) respond(QMailMessage(currentId),QMailMessage::ReplyToAll); } void EmailClient::forwardClicked() { QMailMessageId currentId = readMailWidget()->displayedMessage(); if (currentId.isValid()) respond(QMailMessage(currentId),QMailMessage::Forward); } void EmailClient::respond(const QMailMessage& message, QMailMessage::ResponseType type) { if ((type == QMailMessage::NoResponse) || (type == QMailMessage::ForwardPart)) { qWarning() << "Invalid responseType:" << type; return; } repliedFromMailId = message.id(); if (type == QMailMessage::Reply) { repliedFlags = QMailMessage::Replied; } else if (type == QMailMessage::ReplyToAll) { repliedFlags = QMailMessage::RepliedAll; } else if (type == QMailMessage::Forward) { repliedFlags = QMailMessage::Forwarded; } writeMailWidget()->respond(message, type); if (!writeMailWidget()->composer().isEmpty()) { viewComposer(); } } void EmailClient::respond(const QMailMessagePart::Location& partLocation, QMailMessage::ResponseType type) { if (type != QMailMessage::ForwardPart) { qWarning() << "Invalid responseType:" << type; return; } repliedFromMailId = partLocation.containingMessageId(); repliedFlags = QMailMessage::Forwarded; writeMailWidget()->respond(partLocation, type); if (!writeMailWidget()->composer().isEmpty()) { viewComposer(); } } void EmailClient::modify(const QMailMessage& message) { // Is this type editable? QString key(QMailComposerFactory::defaultKey(message.messageType())); if (!key.isEmpty()) { writeMailWidget()->modify(message); if ( writeMailWidget()->composer().isEmpty() ) { // failed to create new composer, maybe due to no email account // being present. return; } viewComposer(); } else { QMessageBox::warning(0, tr("Error"), tr("Cannot edit a message of this type."), tr("OK")); } } void EmailClient::retrieveMoreMessages() { if (isRetrieving()) { qWarning() << "retrieveMoreMessages called while retrieval in progress"; return; } mailAccountId = QMailAccountId(); QMailFolderId folderId(messageListView()->folderId()); if (folderId.isValid()) { QMailFolder folder(folderId); // Find how many messages we have requested for this folder QMailMessageKey countKey(QMailDisconnected::sourceKey(folderId)); countKey &= ~QMailSearchAction::temporaryKey(); int retrievedMinimum = QMailStore::instance()->countMessages(countKey); // Request more messages retrievedMinimum += QMailRetrievalAction::defaultMinimum(); setRetrievalInProgress(true); retrieveAction("Retrieving message list for folder")->retrieveMessageList(folder.parentAccountId(), folderId, retrievedMinimum); } } void EmailClient::retrieveVisibleMessagesFlags() { if (workOfflineAction->isChecked()) return; // This code to detect flag changes is required to address a limitation // of IMAP servers that do not support NOTIFY+CONDSTORE functionality. QMailMessageIdList ids(messageListView()->visibleMessagesIds()); if (ids.isEmpty()) return; // Ensure that we only ask for flag updates for messages that are retrievable QMailMessageKey idKey(QMailMessageKey::id(ids)); QMailMessageKey retrieveKey(QMailMessageKey::parentAccountId(QMailAccountKey::status(QMailAccount::CanRetrieve, QMailDataComparator::Includes))); QMailMessageKey enabledKey(QMailMessageKey::parentAccountId(QMailAccountKey::status(QMailAccount::Enabled, QMailDataComparator::Includes))); ids = QMailStore::instance()->queryMessages(idKey & retrieveKey & enabledKey); if (ids.isEmpty()) return; if (m_flagRetrievalAction->isRunning()) { // There is a flag retrieval already ocurring; save these IDs to be checked afterwards flagMessageIds += QSet(ids.begin(), ids.end()); } else { m_flagRetrievalAction->retrieveMessages(ids, QMailRetrievalAction::Flags); } } void EmailClient::composeActivated() { delayedInit(); if (writeMailWidget()->prepareComposer(QMailMessage::Email)) viewComposer(); } void EmailClient::sendMessageTo(const QMailAddress &address, QMailMessage::MessageType type) { if (type == QMailMessage::AnyType) type = QMailMessage::Email; // Some address types imply message types if (address.isEmailAddress() && ((type != QMailMessage::Email) && (type != QMailMessage::Mms))) { type = QMailMessage::Email; } if (writeMailWidget()->prepareComposer(type)) { QMailMessage newMessage; newMessage.setTo(QMailAddressList() << address); newMessage.setMessageType(type); writeMailWidget()->create(newMessage); viewComposer(); } } void EmailClient::quit() { if (writeMailWidget()->hasContent()) { // We need to save whatever is currently being worked on writeMailWidget()->forcedClosure(); if (lastDraftId.isValid()) { // Store this value to remind the user on next startup QSettings mailconf("QtProject", "qtmail"); mailconf.beginGroup("restart"); mailconf.setValue("lastDraftId", lastDraftId.toULongLong() ); mailconf.endGroup(); } } if (m_messageServerProcess) { //we started the messageserver, direct it to shut down //before we quit ourselves QMailMessageServer server; server.shutdown(); QTimer::singleShot(0,qApp,SLOT(quit())); } else QApplication::quit(); } void EmailClient::closeEvent(QCloseEvent *e) { if (closeImmediately()) quit(); e->ignore(); } void EmailClient::setupUi() { QWidget* f = new QWidget(this); setCentralWidget(f); QVBoxLayout* vb = new QVBoxLayout(f); vb->setContentsMargins( 0, 0, 0, 0 ); vb->setSpacing( 0 ); QSplitter* horizontalSplitter = new QSplitter(this); horizontalSplitter->setOrientation(Qt::Vertical); QWidget* messageList = new QWidget(this); QVBoxLayout* messageListLayout = new QVBoxLayout(messageList); messageListLayout->setSpacing(0); messageListLayout->setContentsMargins(0,0,0,0); horizontalSplitter->addWidget(messageListView()); horizontalSplitter->addWidget(readMailWidget()); QSplitter* verticalSplitter = new QSplitter(this); verticalSplitter->addWidget(folderView()); verticalSplitter->addWidget(horizontalSplitter); vb->addWidget( verticalSplitter ); setGeometry(0,0,defaultWidth,defaultHeight); int thirdHeight = height() /3; horizontalSplitter->setSizes(QList() << thirdHeight << (height()- thirdHeight)); int quarterWidth = width() /4; verticalSplitter->setSizes(QList() << quarterWidth << (width()- quarterWidth)); //detailed progress widget m_statusMonitorWidget = new StatusMonitorWidget(this, StatusBarHeight,0); m_statusMonitorWidget->hide(); //status bar setStatusBar(new QStatusBar(this)); statusBar()->setMaximumHeight(StatusBarHeight); statusBar()->setStyleSheet("QStatusBar::item { border: 0px;}"); StatusBar* m_statusBar = new StatusBar(this); statusBar()->addPermanentWidget(m_statusBar,100); connect(m_statusBar,SIGNAL(showDetails()), m_statusMonitorWidget,SLOT(show())); connect(m_statusBar,SIGNAL(hideDetails()), m_statusMonitorWidget,SLOT(hide())); connect(this, SIGNAL(updateStatus(QString)), m_statusBar,SLOT(setStatus(QString)) ); connect(StatusMonitor::instance(),SIGNAL(statusChanged(QString)), m_statusBar,SLOT(setStatus(QString))); // connect(this, SIGNAL(updateProgress(uint,uint)), // m_statusBar, SLOT(setProgress(uint,uint)) ); connect(StatusMonitor::instance(),SIGNAL(progressChanged(uint,uint)), m_statusBar,SLOT(setProgress(uint,uint))); connect(this, SIGNAL(clearStatus()), m_statusBar, SLOT(clearStatus())); connect(this, SIGNAL(clearProgress()), m_statusBar, SLOT(clearProgress())); //main menu QMenuBar* mainMenuBar = new QMenuBar(this); QMenu* file = mainMenuBar->addMenu("File"); QMenu* help = mainMenuBar->addMenu("Help"); QAction* aboutQt = help->addAction("About Qt"); aboutQt->setMenuRole(QAction::AboutQtRole); connect(aboutQt,SIGNAL(triggered()),qApp,SLOT(aboutQt())); QWidget* menuWidget = new QWidget(this); QHBoxLayout* menuLayout = new QHBoxLayout(menuWidget); menuLayout->setSpacing(0); menuLayout->setContentsMargins(0,0,5,0); #ifdef Q_OS_MAC menuLayout->addStretch(); #else menuLayout->addWidget(mainMenuBar); #endif //spinner icon QLabel* statusIcon = new ActivityIcon(this); menuLayout->addWidget(statusIcon); setMenuWidget(menuWidget); m_contextMenu = file; //toolbar m_toolBar = new QToolBar(this); addToolBar(m_toolBar); } void EmailClient::showEvent(QShowEvent* e) { Q_UNUSED(e); suspendMailCount = false; QTimer::singleShot(0, this, SLOT(delayedInit()) ); } void EmailClient::setSendingInProgress(bool set) { readMailWidget()->setSendingInProgress(set); if (set) { if (!isRetrieving()) primaryActivity = Sending; } else { if (primaryActivity == Sending) primaryActivity = Inactive; } if (isSending() != set) { int newStatus = (set ? transferStatus | Sending : transferStatus & ~Sending); transferStatusUpdate(newStatus); } } void EmailClient::setRetrievalInProgress(bool set) { readMailWidget()->setRetrievalInProgress(set); if (set) { if (!isSending()) primaryActivity = Retrieving; } else { if (primaryActivity == Retrieving) primaryActivity = Inactive; } if (isRetrieving() != set) { int newStatus = (set ? transferStatus | Retrieving : transferStatus & ~Retrieving); transferStatusUpdate(newStatus); } } void EmailClient::transferStatusUpdate(int status) { if (status != transferStatus) { transferStatus = status; if (transferStatus == Inactive) { if (closeAfterTransmissions) close(); } // UI updates setActionVisible(cancelButton, transferStatus != Inactive); updateGetMailButton(); updateActions(); } } void EmailClient::contextStatusUpdate() { if (isTransmitting()) return; MessageUiBase::contextStatusUpdate(); } void EmailClient::settings() { clearStatusText(); contextStatusUpdate(); AccountSettings settingsDialog(this); settingsDialog.exec(); } void EmailClient::createStandardFolders() { QMailAccountKey retrieveKey(QMailAccountKey::status(QMailAccount::CanRetrieve, QMailDataComparator::Includes)); QMailAccountKey enabledKey(QMailAccountKey::status(QMailAccount::Enabled, QMailDataComparator::Includes)); availableAccounts = QMailStore::instance()->queryAccounts(retrieveKey & enabledKey); if (!availableAccounts.isEmpty()) { foreach(QMailAccountId accountId, availableAccounts) retrieveAction("createStandardfolders")->createStandardFolders(accountId); } } void EmailClient::notificationStateChanged() { #ifndef QT_NO_SYSTEMTRAYICON if (!NotificationTray::isSystemTrayAvailable()) { QMessageBox::warning(this, tr("Unable to enable notification"), tr("System tray was undetected")); notificationAction->setChecked(false); return; } if (!NotificationTray::supportsMessages()) { QMessageBox::warning(this, tr("Unable to enable notification"), tr("System tray doesn't support messages")); notificationAction->setChecked(false); return; } static QScopedPointer tray(new NotificationTray()); if (notificationAction->isChecked()) tray->show(); else tray->hide(); #endif // QT_NO_SYSTEMTRAYICON } void EmailClient::connectionStateChanged() { if (workOfflineAction->isChecked()) return; exportPendingChanges(); sendAllQueuedMail(); } void EmailClient::exportPendingChanges() { if (workOfflineAction->isChecked()) return; foreach(QMailAccountId accountId, emailAccounts()) { exportPendingChanges(accountId); } if (!m_exportAction) { m_exportAction = new QMailRetrievalAction(this); connectServiceAction(m_exportAction); } runNextPendingExport(); } void EmailClient::exportPendingChanges(const QMailAccountId &accountId) { if (workOfflineAction->isChecked()) return; if (!m_queuedExports.contains(accountId)) { m_queuedExports.append(accountId); } } void EmailClient::runNextPendingExport() { if (m_queuedExports.isEmpty()) { m_exportAction->deleteLater(); m_exportAction = 0; return; } if (!m_exportAction->isRunning()) { QMailAccountId mailAccountId = m_queuedExports.first(); ServiceActionStatusItem* newItem = new ServiceActionStatusItem(m_exportAction, "Exporting pending updates"); StatusMonitor::instance()->add(newItem); m_exportAction->exportUpdates(mailAccountId); return; } } void EmailClient::accountsAdded(const QMailAccountIdList&) { updateGetAccountButton(); updateAccounts(); } void EmailClient::accountsRemoved(const QMailAccountIdList&) { updateGetAccountButton(); updateAccounts(); } void EmailClient::accountsUpdated(const QMailAccountIdList&) { updateGetAccountButton(); updateAccounts(); } void EmailClient::messagesUpdated(const QMailMessageIdList& ids) { if (isRetrieving()) { if (ids.contains(readMailWidget()->displayedMessage())) { QMailMessageMetaData updatedMessage(readMailWidget()->displayedMessage()); if (updatedMessage.status() & QMailMessage::Removed) { // This message has been removed QMessageBox::warning(0, tr("Message deleted"), tr("Message cannot be downloaded, because it has been deleted from the server."), QMessageBox::Ok); } } } } void EmailClient::messageSelectionChanged() { if (!moveAction) return; // initActions hasn't been called yet MessageUiBase::messageSelectionChanged(); const bool messagesSelected(selectionCount != 0); // We can delete only if all selected messages are in the Trash folder QMailMessageKey selectedFilter(QMailMessageKey::id(messageListView()->selected())); QMailMessageKey trashFilter(QMailMessageKey::status(QMailMessage::Trash)); const int trashCount(QMailStore::instance()->countMessages(selectedFilter & trashFilter)); const bool deleting(trashCount == selectionCount); int count = messageListView()->rowCount(); if ((count > 0) && (selectionCount > 0)) { if (deleting) { deleteMailAction->setText(tr("Delete message(s)", "", selectionCount)); } else { deleteMailAction->setText(tr("Move to Trash")); } moveAction->setText(tr("Move message(s)...", "", selectionCount)); copyAction->setText(tr("Copy message(s)...", "", selectionCount)); restoreAction->setText(tr("Restore message(s)", "", selectionCount)); } // Ensure that the per-message actions are hidden, if not usable setActionVisible(deleteMailAction, messagesSelected); // We cannot move/copy messages in the trash setActionVisible(moveAction, (messagesSelected && !(trashCount > 0))); setActionVisible(copyAction, (messagesSelected && !(trashCount > 0))); setActionVisible(restoreAction, (messagesSelected && (trashCount > 0))); if (messageListView()->current().isValid()) { QMailMessage mail(messageListView()->current()); bool incoming(mail.status() & QMailMessage::Incoming); bool downloaded(mail.status() & QMailMessage::ContentAvailable); bool system(mail.messageType() == QMailMessage::System); if (!downloaded || system) { // We can't really forward/reply/reply-to-all without the message content setActionVisible(replyAction,false); setActionVisible(replyAllAction,false); setActionVisible(forwardAction,false); } else { bool otherReplyTarget(!mail.cc().isEmpty() || mail.to().count() > 1); setActionVisible(replyAction,incoming); setActionVisible(replyAllAction,incoming & otherReplyTarget); setActionVisible(forwardAction,true); } } // We can detach only if a single non-root message is selected setActionVisible(detachThreadAction, ((selectionCount == 1) && messageListView()->hasParent())); updateActions(); } void EmailClient::noSendAccount(QMailMessage::MessageType type) { QString key(QMailComposerFactory::defaultKey(type)); QString name(QMailComposerFactory::name(key, type)); QMessageBox::warning(0, tr("Send Error"), tr("%1 cannot be sent, because no account has been configured to send with.","%1=MMS/Email/TextMessage").arg(name), QMessageBox::Ok); } void EmailClient::setActionVisible(QAction* action, bool visible) { if (action) actionVisibility[action] = visible; } QMailFolderId EmailClient::containingFolder(const QMailMessageId& id) { QMailMessageMetaData metaData(id); return metaData.parentFolderId(); } QMailAccountIdList EmailClient::emailAccounts() const { QMailAccountKey typeKey(QMailAccountKey::messageType(QMailMessage::Email)); QMailAccountKey enabledKey(QMailAccountKey::status(QMailAccount::Enabled, QMailDataComparator::Includes)); return QMailStore::instance()->queryAccounts(typeKey & enabledKey); } void EmailClient::clearNewMessageStatus(const QMailMessageKey& key) { QMailMessageKey clearNewKey = key & QMailMessageKey::status(QMailMessage::New, QMailDataComparator::Includes); int count = QMailStore::instance()->countMessages(clearNewKey); if (count) { QMailStore::instance()->updateMessagesMetaData(clearNewKey, QMailMessage::New, false); } } QAction* EmailClient::createSeparator() { QAction* sep = new QAction(this); sep->setSeparator(true); return sep; } QMailStorageAction* EmailClient::storageAction(const QString& description) { QMailStorageAction* storageAction = new QMailStorageAction(this); connectServiceAction(storageAction); ServiceActionStatusItem* newItem = new ServiceActionStatusItem(storageAction,description); StatusMonitor::instance()->add(newItem); return storageAction; } QMailRetrievalAction* EmailClient::retrieveAction(const QString& description) { if (!m_retrievalAction) { m_retrievalAction = new QMailRetrievalAction(this); connectServiceAction(m_retrievalAction); } ServiceActionStatusItem* newItem = new ServiceActionStatusItem(m_retrievalAction,description); StatusMonitor::instance()->add(newItem); return m_retrievalAction; } QMailTransmitAction* EmailClient::transmitAction(const QString& description) { if (!m_transmitAction) { m_transmitAction = new QMailTransmitAction(this); connectServiceAction(m_transmitAction); connect(m_transmitAction, SIGNAL(messagesFailedTransmission(QMailMessageIdList, QMailServiceAction::Status::ErrorCode)), this, SLOT(messagesFailedTransmission())); } ServiceActionStatusItem* newItem = new ServiceActionStatusItem(m_transmitAction,description); StatusMonitor::instance()->add(newItem); return m_transmitAction; } void EmailClient::setMarkingMode(bool set) { MessageUiBase::setMarkingMode(set); if (markingMode) { markAction->setText(tr("Cancel")); } else { markAction->setText(tr("Mark messages")); } } void EmailClient::markMessages() { setMarkingMode(!markingMode); } void EmailClient::setThreaded(bool set) { MessageUiBase::setThreaded(set); if (threaded) { threadAction->setText(tr("Unthread messages")); } else { threadAction->setText(tr("Thread messages")); } } void EmailClient::threadMessages() { setThreaded(!threaded); } void EmailClient::synchronizeFolder() { if (selectedFolderId.isValid()) { QMailFolder folder(selectedFolderId); bool excludeFolder = (folder.status() & QMailFolder::SynchronizationEnabled); if (QMailStore *store = QMailStore::instance()) { // Find any subfolders of this folder QMailFolderKey subfolderKey(QMailFolderKey::ancestorFolderIds(selectedFolderId, QMailDataComparator::Includes)); QMailFolderIdList folderIds = QMailStore::instance()->queryFolders(subfolderKey); // Mark all of these folders as {un}synchronized folderIds.append(selectedFolderId); foreach (const QMailFolderId &id, folderIds) { QMailFolder folder(id); folder.setStatus(QMailFolder::SynchronizationEnabled, !excludeFolder); store->updateFolder(&folder); } } // Update the action to reflect the change folderSelected(folderView()->currentItem()); } } void EmailClient::nextMessage() { QWidget *list = messageListView()->findChild("messagelistview"); if (list) { QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Enter, Qt::NoModifier)); } } void EmailClient::previousMessage() { QWidget *list = messageListView()->findChild("messagelistview"); if (list) { QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Enter, Qt::NoModifier)); } } void EmailClient::nextUnreadMessage() { QWidget *list = messageListView()->findChild("messagelistview"); if (list) { QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Plus, Qt::NoModifier, "+")); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Plus, Qt::NoModifier, "+")); } } void EmailClient::previousUnreadMessage() { QWidget *list = messageListView()->findChild("messagelistview"); if (list) { QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Minus, Qt::NoModifier, "-")); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Minus, Qt::NoModifier, "-")); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier)); QApplication::postEvent(list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Enter, Qt::NoModifier)); } } void EmailClient::scrollReaderDown() { QWidget *renderer = readMailWidget()->findChild("renderer"); if (renderer) { QApplication::postEvent(renderer, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QApplication::postEvent(renderer, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier)); } } void EmailClient::scrollReaderUp() { QWidget *renderer = readMailWidget()->findChild("renderer"); if (renderer) { QApplication::postEvent(renderer, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QApplication::postEvent(renderer, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier)); } } void EmailClient::readerMarkMessageAsUnread() { quint64 setMask(0); quint64 unsetMask(QMailMessage::Read); QMailMessageId id(readMailWidget()->displayedMessage()); flagMessage(id, setMask, unsetMask); } void EmailClient::readerMarkMessageAsImportant() { quint64 setMask(QMailMessage::Important); quint64 unsetMask(0); QMailMessageId id(readMailWidget()->displayedMessage()); flagMessage(id, setMask, unsetMask); } void EmailClient::readerMarkMessageAsNotImportant() { quint64 setMask(0); quint64 unsetMask(QMailMessage::Important); QMailMessageId id(readMailWidget()->displayedMessage()); flagMessage(id, setMask, unsetMask); } #ifndef QT_NO_SYSTEMTRAYICON NotificationTray::NotificationTray(QObject *parent) : QSystemTrayIcon(QIcon(":icon/qtmail"), parent) { connect(QMailStore::instance(), SIGNAL(messagesAdded(QMailMessageIdList)), this, SLOT(messagesAdded(QMailMessageIdList))); } void NotificationTray::messagesAdded(const QMailMessageIdList &ids) { Q_ASSERT(ids.size()); Q_ASSERT(!ids.contains(QMailMessageId())); Q_ASSERT(QSystemTrayIcon::supportsMessages()); if (isVisible()) { if (ids.size() == 1) { QMailMessage msg(ids.first()); showMessage(tr("New message from: %1").arg(msg.from().toString()), msg.subject()); } else { Q_ASSERT(ids.size() > 1); showMessage(tr("New messages"), tr("Received %1 new messages").arg(ids.size())); } } } #endif // QT_NO_SYSTEMTRAYICON #include