diff --git a/src/kmkernel.cpp b/src/kmkernel.cpp index 4eb464295..80572f610 100644 --- a/src/kmkernel.cpp +++ b/src/kmkernel.cpp @@ -1,1952 +1,1952 @@ /* */ #include "kmkernel.h" #include "settings/kmailsettings.h" #include "libkdepim/broadcaststatus.h" #include "job/opencomposerjob.h" #include "job/newmessagejob.h" #include "job/opencomposerhiddenjob.h" #include "job/fillcomposerjob.h" #include #include using KPIM::BroadcastStatus; #include "kmstartup.h" #include "kmmainwin.h" #include "editor/composer.h" #include "kmreadermainwin.h" #include "undostack.h" #include "kmmainwidget.h" #include "search/checkindexingmanager.h" #include "libkdepim/recentaddresses.h" using KPIM::RecentAddresses; #include "configuredialog/configuredialog.h" #include "kmcommands.h" #include "kmsystemtray.h" #include #include "mailcommon/mailutil.h" #include "pop3settings.h" #include "MailCommon/FolderTreeView" #include "MailCommon/KMFilterDialog" #include "mailcommonsettings_base.h" #include "mailfilteragentinterface.h" #include "PimCommon/PimUtil" #include "folderarchive/folderarchivemanager.h" #include "sieveimapinterface/kmailsieveimapinstanceinterface.h" // kdepim includes #include "kmail-version.h" // kdepimlibs includes #include #include #include #include #include #include #include #include "mailserviceimpl.h" using KMail::MailServiceImpl; #include "mailcommon/jobscheduler.h" #include #include "messagelistsettings.h" #include "gravatarsettings.h" #include "messagelist/messagelistutil.h" #include "messageviewer/messageviewersettings.h" #include "MessageComposer/AkonadiSender" #include "messagecomposer/messagecomposersettings.h" #include "MessageComposer/MessageHelper" #include "MessageComposer/MessageComposerSettings" #include "PimCommon/PimCommonSettings" #include "PimCommon/AutoCorrection" #include #include "globalsettings_templateparser.h" #include "TemplateParser/TemplatesUtil" #include "mailcommon/foldersettings.h" #include "editor/codec/codecmanager.h" #include #include #include #include #include #include #include "kmail_debug.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 "kmailinterface.h" #include "mailcommon/foldercollectionmonitor.h" #include "imapresourcesettings.h" #include "util.h" #include "MailCommon/MailKernel" #include "searchdialog/searchdescriptionattribute.h" #include "kmail_options.h" using namespace MailCommon; static KMKernel *mySelf = nullptr; static bool s_askingToGoOnline = false; /********************************************************************/ /* Constructor and destructor */ /********************************************************************/ KMKernel::KMKernel(QObject *parent) : QObject(parent) , mConfigureDialog(nullptr) , mMailService(nullptr) , mSystemNetworkStatus(true) , mSystemTray(nullptr) - , mDebugBaloo(false) + , mDebugAkonadiSearch(false) { //Initialize kmail sieveimap interface KSieveUi::SieveImapInstanceInterfaceManager::self()->setSieveImapInstanceInterface(new KMailSieveImapInstanceInterface); - mDebugBaloo = !qEnvironmentVariableIsEmpty("KDEPIM_BALOO_DEBUG"); + mDebugAkonadiSearch = !qEnvironmentVariableIsEmpty("AKONADI_SEARCH_DEBUG"); mSystemNetworkStatus = PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline(); Akonadi::AttributeFactory::registerAttribute(); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kmail")); qCDebug(KMAIL_LOG) << "Starting up..."; mySelf = this; the_firstInstance = true; the_undoStack = nullptr; the_msgSender = nullptr; mFilterEditDialog = nullptr; // make sure that we check for config updates before doing anything else KMKernel::config(); // this shares the kmailrc parsing too (via KSharedConfig), and reads values from it // so better do it here, than in some code where changing the group of config() // would be unexpected KMailSettings::self(); mJobScheduler = new JobScheduler(this); mAutoCorrection = new PimCommon::AutoCorrection(); KMime::setUseOutlookAttachmentEncoding(MessageComposer::MessageComposerSettings::self()->outlookCompatibleAttachments()); // cberzan: this crap moved to CodecManager ====================== mNetCodec = QTextCodec::codecForLocale(); // In the case of Japan. Japanese locale name is "eucjp" but // The Japanese mail systems normally used "iso-2022-jp" of locale name. // We want to change locale name from eucjp to iso-2022-jp at KMail only. // (Introduction to i18n, 6.6 Limit of Locale technology): // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP // is the standard for Internet, and Shift-JIS is the encoding // for Windows and Macintosh. const QByteArray netCodecLower = mNetCodec->name().toLower(); if (netCodecLower == "eucjp" #if defined Q_OS_WIN || defined Q_OS_MACX || netCodecLower == "shift-jis" // OK? #endif ) { mNetCodec = QTextCodec::codecForName("jis7"); } // until here ================================================ Akonadi::Session *session = new Akonadi::Session("KMail Kernel ETM", this); mFolderCollectionMonitor = new FolderCollectionMonitor(session, this); connect(mFolderCollectionMonitor->monitor(), &Akonadi::Monitor::collectionRemoved, this, &KMKernel::slotCollectionRemoved); mEntityTreeModel = new Akonadi::EntityTreeModel(folderCollectionMonitor(), this); mEntityTreeModel->setListFilter(Akonadi::CollectionFetchScope::Enabled); mEntityTreeModel->setItemPopulationStrategy(Akonadi::EntityTreeModel::LazyPopulation); mCollectionModel = new Akonadi::EntityMimeTypeFilterModel(this); mCollectionModel->setSourceModel(mEntityTreeModel); mCollectionModel->addMimeTypeInclusionFilter(Akonadi::Collection::mimeType()); mCollectionModel->setHeaderGroup(Akonadi::EntityTreeModel::CollectionTreeHeaders); mCollectionModel->setDynamicSortFilter(true); mCollectionModel->setSortCaseSensitivity(Qt::CaseInsensitive); connect(folderCollectionMonitor(), SIGNAL(collectionChanged(Akonadi::Collection,QSet)), SLOT(slotCollectionChanged(Akonadi::Collection,QSet))); connect(MailTransport::TransportManager::self(), &MailTransport::TransportManager::transportRemoved, this, &KMKernel::transportRemoved); connect(MailTransport::TransportManager::self(), &MailTransport::TransportManager::transportRenamed, this, &KMKernel::transportRenamed); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/MailDispatcherAgent"), QStringLiteral("org.freedesktop.Akonadi.MailDispatcherAgent"), QStringLiteral( "itemDispatchStarted"), this, SLOT(itemDispatchStarted())); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceStatusChanged, this, &KMKernel::instanceStatusChanged); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceError, this, &KMKernel::slotInstanceError); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceWarning, this, &KMKernel::slotInstanceWarning); connect(Akonadi::AgentManager::self(), &Akonadi::AgentManager::instanceRemoved, this, &KMKernel::slotInstanceRemoved); connect(PimCommon::NetworkManager::self()->networkConfigureManager(), &QNetworkConfigurationManager::onlineStateChanged, this, &KMKernel::slotSystemNetworkStatusChanged); connect(KPIM::ProgressManager::instance(), &KPIM::ProgressManager::progressItemCompleted, this, &KMKernel::slotProgressItemCompletedOrCanceled); connect(KPIM::ProgressManager::instance(), &KPIM::ProgressManager::progressItemCanceled, this, &KMKernel::slotProgressItemCompletedOrCanceled); connect(identityManager(), &KIdentityManagement::IdentityManager::deleted, this, &KMKernel::slotDeleteIdentity); CommonKernel->registerKernelIf(this); CommonKernel->registerSettingsIf(this); CommonKernel->registerFilterIf(this); mFolderArchiveManager = new FolderArchiveManager(this); mIndexedItems = new Akonadi::Search::PIM::IndexedItems(this); mCheckIndexingManager = new CheckIndexingManager(mIndexedItems, this); } KMKernel::~KMKernel() { delete mMailService; mMailService = nullptr; mSystemTray = nullptr; stopAgentInstance(); slotSyncConfig(); delete mAutoCorrection; delete mMailCommonSettings; mySelf = nullptr; } Akonadi::ChangeRecorder *KMKernel::folderCollectionMonitor() const { return mFolderCollectionMonitor->monitor(); } Akonadi::EntityTreeModel *KMKernel::entityTreeModel() const { return mEntityTreeModel; } Akonadi::EntityMimeTypeFilterModel *KMKernel::collectionModel() const { return mCollectionModel; } void KMKernel::setupDBus() { (void)new KmailAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/KMail"), this); mMailService = new MailServiceImpl(); } static QUrl makeAbsoluteUrl(const QString &str, const QString &cwd) { return QUrl::fromUserInput(str, cwd, QUrl::AssumeLocalFile); } bool KMKernel::handleCommandLine(bool noArgsOpensReader, const QStringList &args, const QString &workingDir) { QString to, cc, bcc, subj, body, inReplyTo, replyTo; QStringList customHeaders; QUrl messageFile; QList attachURLs; QString identity; bool mailto = false; bool checkMail = false; bool viewOnly = false; bool calledWithSession = false; // for ignoring '-session foo' // process args: QCommandLineParser parser; kmail_options(&parser); QStringList newargs; bool addAttachmentAttribute = false; for (const QString &argument : qAsConst(args)) { if (argument == QLatin1String("--attach")) { addAttachmentAttribute = true; } else { if (argument.startsWith(QLatin1String("--"))) { addAttachmentAttribute = false; } if (argument.contains(QLatin1Char('@')) || argument.startsWith(QLatin1String("mailto:"))) { //address mustn't be trade as a attachment addAttachmentAttribute = false; } if (addAttachmentAttribute) { newargs.append(QStringLiteral("--attach")); newargs.append(argument); } else { newargs.append(argument); } } } parser.process(newargs); if (parser.isSet(QStringLiteral("subject"))) { subj = parser.value(QStringLiteral("subject")); // if kmail is called with 'kmail -session abc' then this doesn't mean // that the user wants to send a message with subject "ession" but // (most likely) that the user clicked on KMail's system tray applet // which results in KMKernel::raise() calling "kmail kmail newInstance" // via D-Bus which apparently executes the application with the original // command line arguments and those include "-session ..." if // kmail/kontact was restored by session management if (subj == QLatin1String("ession")) { subj.clear(); calledWithSession = true; } else { mailto = true; } } if (parser.isSet(QStringLiteral("cc"))) { mailto = true; cc = parser.value(QStringLiteral("cc")); } if (parser.isSet(QStringLiteral("bcc"))) { mailto = true; bcc = parser.value(QStringLiteral("bcc")); } if (parser.isSet(QStringLiteral("replyTo"))) { mailto = true; replyTo = parser.value(QStringLiteral("replyTo")); } if (parser.isSet(QStringLiteral("msg"))) { mailto = true; const QString file = parser.value(QStringLiteral("msg")); messageFile = makeAbsoluteUrl(file, workingDir); } if (parser.isSet(QStringLiteral("body"))) { mailto = true; body = parser.value(QStringLiteral("body")); } const QStringList attachList = parser.values(QStringLiteral("attach")); if (!attachList.isEmpty()) { mailto = true; QStringList::ConstIterator end = attachList.constEnd(); for (QStringList::ConstIterator it = attachList.constBegin(); it != end; ++it) { if (!(*it).isEmpty()) { if ((*it) != QLatin1String("--")) { attachURLs.append(makeAbsoluteUrl(*it, workingDir)); } } } } customHeaders = parser.values(QStringLiteral("header")); if (parser.isSet(QStringLiteral("composer"))) { mailto = true; } if (parser.isSet(QStringLiteral("check"))) { checkMail = true; } if (parser.isSet(QStringLiteral("identity"))) { identity = parser.value(QStringLiteral("identity")); } if (parser.isSet(QStringLiteral("view"))) { viewOnly = true; const QString filename = parser.value(QStringLiteral("view")); messageFile = QUrl::fromUserInput(filename, workingDir); } if (!calledWithSession) { // only read additional command line arguments if kmail/kontact is // not called with "-session foo" for (const QString &arg : parser.positionalArguments()) { if (arg.startsWith(QStringLiteral("mailto:"), Qt::CaseInsensitive)) { const QUrl urlDecoded(QUrl::fromPercentEncoding(arg.toUtf8())); QMap values = MessageCore::StringUtil::parseMailtoUrl(urlDecoded); QString str = values.value(QStringLiteral("to")); if (!str.isEmpty()) { to += str + QStringLiteral(", "); } str = values.value(QStringLiteral("cc")); if (!str.isEmpty()) { cc += str + QStringLiteral(", "); } str = values.value(QStringLiteral("bcc")); if (!str.isEmpty()) { bcc += str + QStringLiteral(", "); } str = values.value(QStringLiteral("subject")); if (!str.isEmpty()) { subj = str; } str = values.value(QStringLiteral("body")); if (!str.isEmpty()) { body = str; } str = values.value(QStringLiteral("in-reply-to")); if (!str.isEmpty()) { inReplyTo = str; } QString attach = values.value(QStringLiteral("attachment")); if (!attach.isEmpty()) { attachURLs << makeAbsoluteUrl(attach, workingDir); } attach = values.value(QStringLiteral("attach")); if (!attach.isEmpty()) { attachURLs << makeAbsoluteUrl(attach, workingDir); } } else { QUrl url(arg); if (url.isValid() && !url.scheme().isEmpty()) { attachURLs += url; } else { to += arg + QStringLiteral(", "); } } mailto = true; } if (!to.isEmpty()) { // cut off the superfluous trailing ", " to.truncate(to.length() - 2); } } if (!noArgsOpensReader && !mailto && !checkMail && !viewOnly) { return false; } if (viewOnly) { viewMessage(messageFile); } else { action(mailto, checkMail, to, cc, bcc, subj, body, messageFile, attachURLs, customHeaders, replyTo, inReplyTo, identity); } return true; } /********************************************************************/ /* D-Bus-callable, and command line actions */ /********************************************************************/ void KMKernel::checkMail() //might create a new reader but won't show!! { if (!kmkernel->askToGoOnline()) { return; } const QString resourceGroupPattern(QStringLiteral("Resource %1")); const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); for (Akonadi::AgentInstance type : lst) { const QString id = type.identifier(); KConfigGroup group(KMKernel::config(), resourceGroupPattern.arg(id)); if (group.readEntry("IncludeInManualChecks", true)) { if (!type.isOnline()) { type.setIsOnline(true); } if (mResourcesBeingChecked.isEmpty()) { qCDebug(KMAIL_LOG) << "Starting manual mail check"; Q_EMIT startCheckMail(); } if (!mResourcesBeingChecked.contains(id)) { mResourcesBeingChecked.append(id); } type.synchronize(); } } } void KMKernel::openReader() { openReader(false); } void KMKernel::setSystrayIconNotificationsEnabled(bool enabled) { if (mSystemTray) { mSystemTray->setSystrayIconNotificationsEnabled(enabled); } } QStringList KMKernel::accounts() { QStringList accountLst; const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); accountLst.reserve(lst.count()); for (const Akonadi::AgentInstance &type : lst) { // Explicitly make a copy, as we're not changing values of the list but only // the local copy which is passed to action. accountLst << type.identifier(); } return accountLst; } void KMKernel::checkAccount(const QString &account) //might create a new reader but won't show!! { if (account.isEmpty()) { checkMail(); } else { Akonadi::AgentInstance agent = Akonadi::AgentManager::self()->instance(account); if (agent.isValid()) { agent.synchronize(); } else { qCDebug(KMAIL_LOG) << "- account with name '" << account << "' not found"; } } } void KMKernel::openReader(bool onlyCheck) { KMainWindow *ktmw = nullptr; foreach (KMainWindow *window, KMainWindow::memberList()) { if (::qobject_cast(window)) { ktmw = window; break; } } bool activate; if (ktmw) { KMMainWin *win = static_cast(ktmw); activate = !onlyCheck; // existing window: only activate if not --check if (activate) { win->show(); } } else { KMMainWin *win = new KMMainWin; win->show(); activate = false; // new window: no explicit activation (#73591) } } void KMKernel::openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, bool hidden, const QString &messageFile, const QStringList &attachmentPaths, const QStringList &customHeaders, const QString &replyTo, const QString &inReplyTo, const QString &identity) { const OpenComposerSettings settings(to, cc, bcc, subject, body, hidden, messageFile, attachmentPaths, customHeaders, replyTo, inReplyTo, identity); OpenComposerJob *job = new OpenComposerJob(this); job->setOpenComposerSettings(settings); job->start(); } void KMKernel::openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, bool hidden, const QString &attachName, const QByteArray &attachCte, const QByteArray &attachData, const QByteArray &attachType, const QByteArray &attachSubType, const QByteArray &attachParamAttr, const QString &attachParamValue, const QByteArray &attachContDisp, const QByteArray &attachCharset, unsigned int identity) { fillComposer(hidden, to, cc, bcc, subject, body, attachName, attachCte, attachData, attachType, attachSubType, attachParamAttr, attachParamValue, attachContDisp, attachCharset, identity, false); } void KMKernel::openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QString &attachName, const QByteArray &attachCte, const QByteArray &attachData, const QByteArray &attachType, const QByteArray &attachSubType, const QByteArray &attachParamAttr, const QString &attachParamValue, const QByteArray &attachContDisp, const QByteArray &attachCharset, unsigned int identity) { fillComposer(false, to, cc, bcc, subject, body, attachName, attachCte, attachData, attachType, attachSubType, attachParamAttr, attachParamValue, attachContDisp, attachCharset, identity, true); } void KMKernel::fillComposer(bool hidden, const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QString &attachName, const QByteArray &attachCte, const QByteArray &attachData, const QByteArray &attachType, const QByteArray &attachSubType, const QByteArray &attachParamAttr, const QString &attachParamValue, const QByteArray &attachContDisp, const QByteArray &attachCharset, unsigned int identity, bool forceShowWindow) { const FillComposerJobSettings settings(hidden, to, cc, bcc, subject, body, attachName, attachCte, attachData, attachType, attachSubType, attachParamAttr, attachParamValue, attachContDisp, attachCharset, identity, forceShowWindow); FillComposerJob *job = new FillComposerJob; job->setSettings(settings); job->start(); } void KMKernel::openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, bool hidden) { const OpenComposerHiddenJobSettings settings(to, cc, bcc, subject, body, hidden); OpenComposerHiddenJob *job = new OpenComposerHiddenJob(this); job->setSettings(settings); job->start(); } void KMKernel::newMessage(const QString &to, const QString &cc, const QString &bcc, bool hidden, bool useFolderId, const QString & /*messageFile*/, const QString &_attachURL) { QSharedPointer folder; Akonadi::Collection col; uint id = 0; if (useFolderId) { //create message with required folder identity folder = currentFolderCollection(); id = folder ? folder->identity() : 0; col = currentCollection(); } const NewMessageJobSettings settings(to, cc, bcc, hidden, _attachURL, folder, id, col); NewMessageJob *job = new NewMessageJob(this); job->setNewMessageJobSettings(settings); job->start(); } void KMKernel::viewMessage(const QUrl &url) { KMOpenMsgCommand *openCommand = new KMOpenMsgCommand(nullptr, url); openCommand->start(); } int KMKernel::viewMessage(const QString &messageFile) { KMOpenMsgCommand *openCommand = new KMOpenMsgCommand(nullptr, QUrl::fromLocalFile(messageFile)); openCommand->start(); return 1; } void KMKernel::raise() { QDBusInterface iface(QStringLiteral("org.kde.kmail"), QStringLiteral("/MainApplication"), QStringLiteral("org.kde.PIMUniqueApplication"), QDBusConnection::sessionBus()); QDBusReply reply; if (!iface.isValid() || !(reply = iface.call(QStringLiteral("newInstance"))).isValid()) { QDBusError err = iface.lastError(); qCritical() << "Communication problem with KMail. " << "Error message was:" << err.name() << ": \"" << err.message() << "\""; } } bool KMKernel::showMail(qint64 serialNumber) { KMMainWidget *mainWidget = nullptr; // First look for a KMainWindow. foreach (KMainWindow *window, KMainWindow::memberList()) { // Then look for a KMMainWidget. QList l = window->findChildren(); if (!l.isEmpty() && l.first()) { mainWidget = l.first(); if (window->isActiveWindow()) { break; } } } if (mainWidget) { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(Akonadi::Item(serialNumber), this); job->fetchScope().fetchFullPayload(); if (job->exec()) { if (job->items().count() >= 1) { KMReaderMainWin *win = new KMReaderMainWin(MessageViewer::Viewer::UseGlobalSetting, false); win->showMessage(MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(), job->items().at(0)); win->show(); return true; } } } return false; } void KMKernel::pauseBackgroundJobs() { mBackgroundTasksTimer->stop(); mJobScheduler->pause(); } void KMKernel::resumeBackgroundJobs() { mJobScheduler->resume(); mBackgroundTasksTimer->start(4 * 60 * 60 * 1000); } void KMKernel::stopNetworkJobs() { if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Offline) { return; } setAccountStatus(false); KMailSettings::setNetworkState(KMailSettings::EnumNetworkState::Offline); BroadcastStatus::instance()->setStatusMsg(i18n("KMail is set to be offline; all network jobs are suspended")); Q_EMIT onlineStatusChanged((KMailSettings::EnumNetworkState::type)KMailSettings::networkState()); } void KMKernel::setAccountStatus(bool goOnline) { const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(false); for (Akonadi::AgentInstance type : lst) { const QString identifier(type.identifier()); if (PimCommon::Util::isImapResource(identifier) || identifier.contains(POP3_RESOURCE_IDENTIFIER) || identifier.contains(QStringLiteral("akonadi_maildispatcher_agent")) || type.type().capabilities().contains(QStringLiteral("NeedsNetwork"))) { type.setIsOnline(goOnline); } } if (goOnline && MessageComposer::MessageComposerSettings::self()->sendImmediate()) { const auto col = CommonKernel->collectionFromId(CommonKernel->outboxCollectionFolder().id()); const qint64 nbMsgOutboxCollection = col.statistics().count(); if (nbMsgOutboxCollection > 0) { kmkernel->msgSender()->sendQueued(); } } } const QString KMKernel::xmlGuiInstanceName() const { return mXmlGuiInstance; } void KMKernel::setXmlGuiInstanceName(const QString &instance) { mXmlGuiInstance = instance; } KMail::UndoStack *KMKernel::undoStack() const { return the_undoStack; } void KMKernel::resumeNetworkJobs() { if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Online) { return; } if (mSystemNetworkStatus) { setAccountStatus(true); BroadcastStatus::instance()->setStatusMsg(i18n("KMail is set to be online; all network jobs resumed")); } else { BroadcastStatus::instance()->setStatusMsg(i18n("KMail is set to be online; all network jobs will resume when a network connection is detected")); } KMailSettings::setNetworkState(KMailSettings::EnumNetworkState::Online); Q_EMIT onlineStatusChanged((KMailSettings::EnumNetworkState::type)KMailSettings::networkState()); KMMainWidget *widget = getKMMainWidget(); if (widget) { widget->refreshMessageListSelection(); } } bool KMKernel::isOffline() { if ((KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Offline) || !PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) { return true; } else { return false; } } void KMKernel::verifyAccount() { const QString resourceGroupPattern(QStringLiteral("Resource %1")); const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); for (Akonadi::AgentInstance type : lst) { KConfigGroup group(KMKernel::config(), resourceGroupPattern.arg(type.identifier())); if (group.readEntry("CheckOnStartup", false)) { if (!type.isOnline()) { type.setIsOnline(true); } type.synchronize(); } // "false" is also hardcoded in ConfigureDialog, don't forget to change there. if (group.readEntry("OfflineOnShutdown", false)) { if (!type.isOnline()) { type.setIsOnline(true); } } } } void KMKernel::slotCheckAccount(Akonadi::ServerManager::State state) { if (state == Akonadi::ServerManager::Running) { disconnect(Akonadi::ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State))); verifyAccount(); } } void KMKernel::checkMailOnStartup() { if (!kmkernel->askToGoOnline()) { return; } if (Akonadi::ServerManager::state() != Akonadi::ServerManager::Running) { connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &KMKernel::slotCheckAccount); } else { verifyAccount(); } } bool KMKernel::askToGoOnline() { // already asking means we are offline and need to wait anyhow if (s_askingToGoOnline) { return false; } if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Offline) { s_askingToGoOnline = true; int rc = KMessageBox::questionYesNo(KMKernel::self()->mainWin(), i18n("KMail is currently in offline mode. " "How do you want to proceed?"), i18n("Online/Offline"), KGuiItem(i18n("Work Online")), KGuiItem(i18n("Work Offline"))); s_askingToGoOnline = false; if (rc == KMessageBox::No) { return false; } else { kmkernel->resumeNetworkJobs(); } } if (kmkernel->isOffline()) { return false; } return true; } void KMKernel::slotSystemNetworkStatusChanged(bool isOnline) { mSystemNetworkStatus = isOnline; if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Offline) { return; } if (isOnline) { BroadcastStatus::instance()->setStatusMsg(i18n( "Network connection detected, all network jobs resumed")); kmkernel->setAccountStatus(true); } else { BroadcastStatus::instance()->setStatusMsg(i18n( "No network connection detected, all network jobs are suspended")); kmkernel->setAccountStatus(false); } } /********************************************************************/ /* Kernel methods */ /********************************************************************/ void KMKernel::quit() { // Called when all windows are closed. Will take care of compacting, // sending... should handle session management too!! } /* TODO later: Asuming that: - msgsender is nonblocking (our own, QSocketNotifier based. Pops up errors and sends signal senderFinished when done) o If we are getting mail, stop it (but don't lose something!) [Done already, see mailCheckAborted] o If we are sending mail, go on UNLESS this was called by SM, in which case stop ASAP that too (can we warn? should we continue on next start?) o If we are compacting, or expunging, go on UNLESS this was SM call. In that case stop compacting ASAP and continue on next start, before touching any folders. [Not needed anymore with CompactionJob] KMKernel::quit () { SM call? if compacting, stop; if sending, stop; if receiving, stop; Windows will take care of themselves (composer should dump its messages, if any but not in deadMail) declare us ready for the End of the Session No, normal quit call All windows are off. Anything to do, should compact or sender sends? Yes, maybe put an icon in panel as a sign of life if sender sending, connect us to his finished slot, declare us ready for quit and wait for senderFinished if not, Folder manager, go compact sent-mail and outbox } (= call slotFinished()) void KMKernel::slotSenderFinished() { good, Folder manager go compact sent-mail and outbox clean up stage1 (release folders and config, unregister from dcop) -- another kmail may start now --- qApp->quit(); } */ /********************************************************************/ /* Init, Exit, and handler methods */ /********************************************************************/ //----------------------------------------------------------------------------- // Open a composer for each message found in the dead.letter folder void KMKernel::recoverDeadLetters() { const QString pathName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/"); QDir dir(pathName); if (!dir.exists(QStringLiteral("autosave"))) { return; } dir.cd(pathName + QLatin1String("autosave")); const QFileInfoList autoSaveFiles = dir.entryInfoList(); for (const QFileInfo &file : autoSaveFiles) { // Disregard the '.' and '..' folders const QString filename = file.fileName(); if (filename == QLatin1String(".") || filename == QLatin1String("..") || file.isDir()) { continue; } qCDebug(KMAIL_LOG) << "Opening autosave file:" << file.absoluteFilePath(); QFile autoSaveFile(file.absoluteFilePath()); if (autoSaveFile.open(QIODevice::ReadOnly)) { const KMime::Message::Ptr autoSaveMessage(new KMime::Message()); const QByteArray msgData = autoSaveFile.readAll(); autoSaveMessage->setContent(msgData); autoSaveMessage->parse(); // Show the a new composer dialog for the message KMail::Composer *autoSaveWin = KMail::makeComposer(); autoSaveWin->setMessage(autoSaveMessage, false, false, false); autoSaveWin->setAutoSaveFileName(filename); autoSaveWin->show(); autoSaveFile.close(); } else { KMessageBox::sorry(nullptr, i18n("Failed to open autosave file at %1.\nReason: %2", file.absoluteFilePath(), autoSaveFile.errorString()), i18n("Opening Autosave File Failed")); } } } void KMKernel::akonadiStateChanged(Akonadi::ServerManager::State state) { qCDebug(KMAIL_LOG) << "KMKernel has akonadi state changed to:" << int(state); if (state == Akonadi::ServerManager::Running) { CommonKernel->initFolders(); } } static void kmCrashHandler(int sigId) { fprintf(stderr, "*** KMail got signal %d (Exiting)\n", sigId); // try to cleanup all windows if (kmkernel) { kmkernel->dumpDeadLetters(); fprintf(stderr, "*** Dead letters dumped.\n"); kmkernel->stopAgentInstance(); kmkernel->cleanupTemporaryFiles(); } } void KMKernel::init() { the_shuttingDown = false; the_firstStart = KMailSettings::self()->firstStart(); KMailSettings::self()->setFirstStart(false); the_previousVersion = KMailSettings::self()->previousVersion(); KMailSettings::self()->setPreviousVersion(QStringLiteral(KDEPIM_VERSION)); the_undoStack = new UndoStack(20); the_msgSender = new MessageComposer::AkonadiSender; // filterMgr->dump(); mBackgroundTasksTimer = new QTimer(this); mBackgroundTasksTimer->setSingleShot(true); connect(mBackgroundTasksTimer, &QTimer::timeout, this, &KMKernel::slotRunBackgroundTasks); #ifdef DEBUG_SCHEDULER // for debugging, see jobscheduler.h mBackgroundTasksTimer->start(10000); // 10s, singleshot #else mBackgroundTasksTimer->start(5 * 60000); // 5 minutes, singleshot #endif KCrash::setEmergencySaveFunction(kmCrashHandler); qCDebug(KMAIL_LOG) << "KMail init with akonadi server state:" << int(Akonadi::ServerManager::state()); if (Akonadi::ServerManager::state() == Akonadi::ServerManager::Running) { CommonKernel->initFolders(); } connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &KMKernel::akonadiStateChanged); } bool KMKernel::doSessionManagement() { // Do session management if (qApp->isSessionRestored()) { int n = 1; while (KMMainWin::canBeRestored(n)) { //only restore main windows! (Matthias); if (KMMainWin::classNameOfToplevel(n) == QLatin1String("KMMainWin")) { (new KMMainWin)->restoreDockedState(n); } ++n; } return true; // we were restored by SM } return false; // no, we were not restored } bool KMKernel::firstInstance() const { return the_firstInstance; } void KMKernel::setFirstInstance(bool value) { the_firstInstance = value; } void KMKernel::closeAllKMailWindows() { QList windowsToDelete; foreach (KMainWindow *window, KMainWindow::memberList()) { if (::qobject_cast(window) || ::qobject_cast(window)) { // close and delete the window window->setAttribute(Qt::WA_DeleteOnClose); window->close(); windowsToDelete.append(window); } } // We delete all main windows here. Above we called close(), but that calls // deleteLater() internally, therefore does not delete it immediately. // This would lead to problems when closing Kontact when a composer window // is open, because the destruction order is: // // 1. destructor of the Kontact mainwinow // 2. delete all parts // 3. the KMail part destructor calls KMKernel::cleanup(), which calls // this function // 4. delete all other mainwindows // // Deleting the composer windows here will make sure that step 4 will not delete // any composer window, which would fail because the kernel is already deleted. qDeleteAll(windowsToDelete); windowsToDelete.clear(); } void KMKernel::cleanup(void) { disconnect(Akonadi::AgentManager::self(), SIGNAL(instanceStatusChanged(Akonadi::AgentInstance))); disconnect(Akonadi::AgentManager::self(), SIGNAL(instanceError(Akonadi::AgentInstance,QString))); disconnect(Akonadi::AgentManager::self(), SIGNAL(instanceWarning(Akonadi::AgentInstance,QString))); disconnect(Akonadi::AgentManager::self(), SIGNAL(instanceRemoved(Akonadi::AgentInstance))); disconnect(KPIM::ProgressManager::instance(), SIGNAL(progressItemCompleted(KPIM::ProgressItem *))); disconnect(KPIM::ProgressManager::instance(), SIGNAL(progressItemCanceled(KPIM::ProgressItem *))); dumpDeadLetters(); the_shuttingDown = true; closeAllKMailWindows(); // Flush the cache of foldercollection objects. This results // in configuration writes, so we need to do it early enough. MailCommon::FolderSettings::clearCache(); // Write the config while all other managers are alive delete the_msgSender; the_msgSender = nullptr; delete the_undoStack; the_undoStack = nullptr; delete mConfigureDialog; mConfigureDialog = nullptr; KSharedConfig::Ptr config = KMKernel::config(); if (RecentAddresses::exists()) { RecentAddresses::self(config.data())->save(config.data()); } Akonadi::Collection trashCollection = CommonKernel->trashCollectionFolder(); if (trashCollection.isValid()) { if (KMailSettings::self()->emptyTrashOnExit()) { OrgFreedesktopAkonadiMailFilterAgentInterface mailFilterInterface(QStringLiteral("org.freedesktop.Akonadi.MailFilterAgent"), QStringLiteral( "/MailFilterAgent"), QDBusConnection::sessionBus(), this); if (mailFilterInterface.isValid()) { mailFilterInterface.expunge((qlonglong)trashCollection.id()); } else { qCWarning(KMAIL_LOG) << "Mailfilter is not active"; } } } } void KMKernel::dumpDeadLetters() { if (shuttingDown()) { return; //All documents should be saved before shutting down is set! } // make all composer windows autosave their contents foreach (KMainWindow *window, KMainWindow::memberList()) { if (KMail::Composer *win = ::qobject_cast(window)) { win->autoSaveMessage(true); while (win->isComposing()) { qCWarning(KMAIL_LOG) << "Danger, using an event loop, this should no longer be happening!"; qApp->processEvents(); } } } } void KMKernel::action(bool mailto, bool check, const QString &to, const QString &cc, const QString &bcc, const QString &subj, const QString &body, const QUrl &messageFile, const QList &attachURLs, const QStringList &customHeaders, const QString &replyTo, const QString &inReplyTo, const QString &identity) { if (mailto) { openComposer(to, cc, bcc, subj, body, 0, messageFile.toLocalFile(), QUrl::toStringList(attachURLs), customHeaders, replyTo, inReplyTo, identity); } else { openReader(check); } if (check) { checkMail(); } //Anything else? } void KMKernel::slotRequestConfigSync() { // ### FIXME: delay as promised in the kdoc of this function ;-) slotSyncConfig(); } void KMKernel::slotSyncConfig() { PimCommon::PimCommonSettings::self()->save(); MessageCore::MessageCoreSettings::self()->save(); MessageViewer::MessageViewerSettings::self()->save(); MessageComposer::MessageComposerSettings::self()->save(); TemplateParser::TemplateParserSettings::self()->save(); MessageList::MessageListSettings::self()->save(); mMailCommonSettings->save(); Gravatar::GravatarSettings::self()->save(); KMailSettings::self()->save(); KMKernel::config()->sync(); //Laurent investigate why we need to reload them. PimCommon::PimCommonSettings::self()->load(); MessageCore::MessageCoreSettings::self()->load(); MessageViewer::MessageViewerSettings::self()->load(); MessageComposer::MessageComposerSettings::self()->load(); TemplateParser::TemplateParserSettings::self()->load(); MessageList::MessageListSettings::self()->load(); mMailCommonSettings->load(); Gravatar::GravatarSettings::self()->load(); KMailSettings::self()->load(); KMKernel::config()->reparseConfiguration(); } void KMKernel::updateConfig() { slotConfigChanged(); } void KMKernel::slotShowConfigurationDialog() { if (KMKernel::getKMMainWidget() == nullptr) { // ensure that there is a main widget available // as parts of the configure dialog (identity) rely on this // and this slot can be called when there is only a KMComposeWin showing KMMainWin *win = new KMMainWin; win->show(); } if (!mConfigureDialog) { mConfigureDialog = new ConfigureDialog(nullptr, false); mConfigureDialog->setObjectName(QStringLiteral("configure")); connect(mConfigureDialog, &ConfigureDialog::configChanged, this, &KMKernel::slotConfigChanged); } // Save all current settings. if (getKMMainWidget()) { getKMMainWidget()->writeReaderConfig(); } if (mConfigureDialog->isHidden()) { mConfigureDialog->show(); } else { mConfigureDialog->raise(); } } void KMKernel::slotConfigChanged() { CodecManager::self()->updatePreferredCharsets(); Q_EMIT configChanged(); } //------------------------------------------------------------------------------- bool KMKernel::haveSystemTrayApplet() const { return mSystemTray != nullptr; } QTextCodec *KMKernel::networkCodec() const { return mNetCodec; } void KMKernel::updateSystemTray() { if (mSystemTray && !the_shuttingDown) { mSystemTray->updateSystemTray(); } } KIdentityManagement::IdentityManager *KMKernel::identityManager() { return KIdentityManagement::IdentityManager::self(); } KMainWindow *KMKernel::mainWin() { // First look for a KMMainWin. foreach (KMainWindow *window, KMainWindow::memberList()) { if (::qobject_cast(window)) { return window; } } // There is no KMMainWin. Use any other KMainWindow instead (e.g. in // case we are running inside Kontact) because we anyway only need // it for modal message boxes and for KNotify events. if (!KMainWindow::memberList().isEmpty()) { KMainWindow *kmWin = KMainWindow::memberList().first(); if (kmWin) { return kmWin; } } // There's not a single KMainWindow. Create a KMMainWin. // This could happen if we want to pop up an error message // while we are still doing the startup wizard and no other // KMainWindow is running. return new KMMainWin; } KMKernel *KMKernel::self() { return mySelf; } KSharedConfig::Ptr KMKernel::config() { assert(mySelf); if (!mySelf->mConfig) { mySelf->mConfig = KSharedConfig::openConfig(QStringLiteral("kmail2rc")); // Check that all updates have been run on the config file: MessageList::MessageListSettings::self()->setSharedConfig(mySelf->mConfig); MessageList::MessageListSettings::self()->load(); TemplateParser::TemplateParserSettings::self()->setSharedConfig(mySelf->mConfig); TemplateParser::TemplateParserSettings::self()->load(); MessageComposer::MessageComposerSettings::self()->setSharedConfig(mySelf->mConfig); MessageComposer::MessageComposerSettings::self()->load(); MessageCore::MessageCoreSettings::self()->setSharedConfig(mySelf->mConfig); MessageCore::MessageCoreSettings::self()->load(); MessageViewer::MessageViewerSettings::self()->setSharedConfig(mySelf->mConfig); MessageViewer::MessageViewerSettings::self()->load(); mMailCommonSettings = new MailCommon::MailCommonSettings; mMailCommonSettings->setSharedConfig(mySelf->mConfig); mMailCommonSettings->load(); PimCommon::PimCommonSettings::self()->setSharedConfig(mySelf->mConfig); PimCommon::PimCommonSettings::self()->load(); Gravatar::GravatarSettings::self()->setSharedConfig(mySelf->mConfig); Gravatar::GravatarSettings::self()->load(); } return mySelf->mConfig; } void KMKernel::syncConfig() { slotRequestConfigSync(); } void KMKernel::selectCollectionFromId(Akonadi::Collection::Id id) { KMMainWidget *widget = getKMMainWidget(); Q_ASSERT(widget); if (!widget) { return; } Akonadi::Collection colFolder = CommonKernel->collectionFromId(id); if (colFolder.isValid()) { widget->slotSelectCollectionFolder(colFolder); } } bool KMKernel::selectFolder(const QString &folder) { KMMainWidget *widget = getKMMainWidget(); Q_ASSERT(widget); if (!widget) { return false; } const Akonadi::Collection colFolder = CommonKernel->collectionFromId(folder.toLongLong()); if (colFolder.isValid()) { widget->slotSelectCollectionFolder(colFolder); return true; } return false; } KMMainWidget *KMKernel::getKMMainWidget() { //This could definitely use a speadup const QWidgetList l = QApplication::topLevelWidgets(); for (QWidget *wid : l) { QList l2 = wid->window()->findChildren(); if (!l2.isEmpty() && l2.first()) { return l2.first(); } } return nullptr; } void KMKernel::slotRunBackgroundTasks() // called regularly by timer { // Hidden KConfig keys. Not meant to be used, but a nice fallback in case // a stable kmail release goes out with a nasty bug in CompactionJob... if (KMailSettings::self()->autoExpiring()) { mFolderCollectionMonitor->expireAllFolders(false /*scheduled, not immediate*/, entityTreeModel()); } if (KMailSettings::self()->checkCollectionsIndexing()) { mCheckIndexingManager->start(entityTreeModel()); } #ifdef DEBUG_SCHEDULER // for debugging, see jobscheduler.h mBackgroundTasksTimer->start(60 * 1000); // check again in 1 minute #else mBackgroundTasksTimer->start(4 * 60 * 60 * 1000); // check again in 4 hours #endif } static Akonadi::Collection::List collect_collections(const QAbstractItemModel *model, const QModelIndex &parent) { Akonadi::Collection::List collections; QStack stack; stack.push(parent); while (!stack.isEmpty()) { const auto idx = stack.pop(); if (idx.isValid()) { collections << model->data(idx, Akonadi::EntityTreeModel::CollectionRole).value(); for (int i = model->rowCount(idx) - 1; i >= 0; --i) { stack.push(idx.child(i, 0)); } } } return collections; } Akonadi::Collection::List KMKernel::allFolders() const { return collect_collections(collectionModel(), QModelIndex()); } Akonadi::Collection::List KMKernel::subfolders(const Akonadi::Collection &col) const { const auto idx = collectionModel()->match({}, Akonadi::EntityTreeModel::CollectionRole, QVariant::fromValue(col), 1, Qt::MatchExactly); if (!idx.isEmpty()) { return collect_collections(collectionModel(), idx[0]); } return {}; } void KMKernel::expireAllFoldersNow() // called by the GUI { mFolderCollectionMonitor->expireAllFolders(true /*immediate*/, entityTreeModel()); } bool KMKernel::canQueryClose() { if (KMMainWidget::mainWidgetList() && KMMainWidget::mainWidgetList()->count() > 1) { return true; } if (!mSystemTray) { return true; } if (mSystemTray->hasUnreadMail()) { mSystemTray->setStatus(KStatusNotifierItem::Active); } mSystemTray->hideKMail(); return false; } Akonadi::Collection KMKernel::currentCollection() { KMMainWidget *widget = getKMMainWidget(); Akonadi::Collection col; if (widget) { col = widget->currentCollection(); } return col; } QSharedPointer KMKernel::currentFolderCollection() { KMMainWidget *widget = getKMMainWidget(); QSharedPointer folder; if (widget) { folder = widget->currentFolder(); } return folder; } MailCommon::MailCommonSettings *KMKernel::mailCommonSettings() const { return mMailCommonSettings; } Akonadi::Search::PIM::IndexedItems *KMKernel::indexedItems() const { return mIndexedItems; } // can't be inline, since KMSender isn't known to implement // KMail::MessageSender outside this .cpp file MessageComposer::MessageSender *KMKernel::msgSender() { return the_msgSender; } void KMKernel::transportRemoved(int id, const QString &name) { Q_UNUSED(id); // reset all identities using the deleted transport QStringList changedIdents; KIdentityManagement::IdentityManager *im = identityManager(); KIdentityManagement::IdentityManager::Iterator end = im->modifyEnd(); for (KIdentityManagement::IdentityManager::Iterator it = im->modifyBegin(); it != end; ++it) { if (name == (*it).transport()) { (*it).setTransport(QString()); changedIdents += (*it).identityName(); } } // if the deleted transport is the currently used transport reset it to default const QString ¤tTransport = KMailSettings::self()->currentTransport(); if (name == currentTransport) { KMailSettings::self()->setCurrentTransport(QString()); } if (!changedIdents.isEmpty()) { QString information = i18np("This identity has been changed to use the default transport:", "These %1 identities have been changed to use the default transport:", changedIdents.count()); //Don't set parent otherwise we will swith to current KMail and we configure it. So not good KMessageBox::informationList(nullptr, information, changedIdents); im->commit(); } } void KMKernel::transportRenamed(int id, const QString &oldName, const QString &newName) { Q_UNUSED(id); QStringList changedIdents; KIdentityManagement::IdentityManager *im = identityManager(); KIdentityManagement::IdentityManager::Iterator end = im->modifyEnd(); for (KIdentityManagement::IdentityManager::Iterator it = im->modifyBegin(); it != end; ++it) { if (oldName == (*it).transport()) { (*it).setTransport(newName); changedIdents << (*it).identityName(); } } if (!changedIdents.isEmpty()) { const QString information = i18np("This identity has been changed to use the modified transport:", "These %1 identities have been changed to use the modified transport:", changedIdents.count()); //Don't set parent otherwise we will swith to current KMail and we configure it. So not good KMessageBox::informationList(nullptr, information, changedIdents); im->commit(); } } void KMKernel::itemDispatchStarted() { // Watch progress of the MDA. KPIM::ProgressManagerAkonadi::createProgressItem(nullptr, MailTransport::DispatcherInterface().dispatcherInstance(), QStringLiteral("Sender"), i18n("Sending messages"), i18n("Initiating sending process..."), true, KPIM::ProgressItem::Unknown); } void KMKernel::instanceStatusChanged(const Akonadi::AgentInstance &instance) { if (instance.identifier() == QLatin1String("akonadi_mailfilter_agent")) { // Creating a progress item twice is ok, it will simply return the already existing // item KPIM::ProgressItem *progress = KPIM::ProgressManagerAkonadi::createProgressItem(nullptr, instance, instance.identifier(), instance.name(), instance.statusMessage(), false, KPIM::ProgressItem::Encrypted); progress->setProperty("AgentIdentifier", instance.identifier()); return; } if (MailCommon::Util::agentInstances(true).contains(instance)) { if (instance.status() == Akonadi::AgentInstance::Running) { if (mResourcesBeingChecked.isEmpty()) { qCDebug(KMAIL_LOG) << "A Resource started to synchronize, starting a mail check."; Q_EMIT startCheckMail(); } const QString identifier(instance.identifier()); if (!mResourcesBeingChecked.contains(identifier)) { mResourcesBeingChecked.append(identifier); } KPIM::ProgressItem::CryptoStatus cryptoStatus = KPIM::ProgressItem::Unencrypted; if (mResourceCryptoSettingCache.contains(identifier)) { cryptoStatus = mResourceCryptoSettingCache.value(identifier); } else { if (PimCommon::Util::isImapResource(identifier)) { MailCommon::ResourceReadConfigFile resourceFile(identifier); const KConfigGroup grp = resourceFile.group(QStringLiteral("network")); if (grp.isValid()) { const QString imapSafety = grp.readEntry(QStringLiteral("Safety")); if (imapSafety == QLatin1String("None")) { cryptoStatus = KPIM::ProgressItem::Unencrypted; } else { cryptoStatus = KPIM::ProgressItem::Encrypted; } mResourceCryptoSettingCache.insert(identifier, cryptoStatus); } } else if (identifier.contains(POP3_RESOURCE_IDENTIFIER)) { MailCommon::ResourceReadConfigFile resourceFile(identifier); const KConfigGroup grp = resourceFile.group(QStringLiteral("General")); if (grp.isValid()) { if (grp.readEntry(QStringLiteral("useSSL"), false) || grp.readEntry(QStringLiteral("useTLS"), false)) { cryptoStatus = KPIM::ProgressItem::Encrypted; } mResourceCryptoSettingCache.insert(identifier, cryptoStatus); } } } // Creating a progress item twice is ok, it will simply return the already existing // item KPIM::ProgressItem *progress = KPIM::ProgressManagerAkonadi::createProgressItem(nullptr, instance, instance.identifier(), instance.name(), instance.statusMessage(), true, cryptoStatus); progress->setProperty("AgentIdentifier", instance.identifier()); } else if (instance.status() == Akonadi::AgentInstance::Broken) { agentInstanceBroken(instance); } } } void KMKernel::agentInstanceBroken(const Akonadi::AgentInstance &instance) { const QString summary = i18n("Resource %1 is broken.", instance.name()); KNotification::event(QStringLiteral("akonadi-resource-broken"), summary, QPixmap(), nullptr, KNotification::CloseOnTimeout); } void KMKernel::slotProgressItemCompletedOrCanceled(KPIM::ProgressItem *item) { const QString identifier = item->property("AgentIdentifier").toString(); const Akonadi::AgentInstance agent = Akonadi::AgentManager::self()->instance(identifier); if (agent.isValid()) { mResourcesBeingChecked.removeAll(identifier); if (mResourcesBeingChecked.isEmpty()) { qCDebug(KMAIL_LOG) << "Last resource finished syncing, mail check done"; Q_EMIT endCheckMail(); } } } void KMKernel::updatedTemplates() { Q_EMIT customTemplatesChanged(); } void KMKernel::cleanupTemporaryFiles() { QDir dir(QDir::tempPath()); const QStringList lst = dir.entryList(QStringList{QStringLiteral("messageviewer_*")}); qCDebug(KMAIL_LOG) << " list file to delete " << lst; for (const QString &file : lst) { QDir tempDir(QDir::tempPath() + QLatin1Char('/') + file); if (!tempDir.removeRecursively()) { fprintf(stderr, "%s was not removed .\n", qPrintable(tempDir.absolutePath())); } else { fprintf(stderr, "%s was removed .\n", qPrintable(tempDir.absolutePath())); } } } void KMKernel::stopAgentInstance() { const QString resourceGroupPattern(QStringLiteral("Resource %1")); const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); for (Akonadi::AgentInstance type : lst) { const QString identifier = type.identifier(); KConfigGroup group(KMKernel::config(), resourceGroupPattern.arg(identifier)); // Keep sync in ConfigureDialog, don't forget to change there. if (group.readEntry("OfflineOnShutdown", identifier.startsWith(QStringLiteral("akonadi_pop3_resource")) ? true : false)) { type.setIsOnline(false); } } } void KMKernel::slotCollectionRemoved(const Akonadi::Collection &col) { KConfigGroup group(KMKernel::config(), MailCommon::FolderSettings::configGroupName(col)); group.deleteGroup(); group.sync(); const QString colStr = QString::number(col.id()); TemplateParser::Util::deleteTemplate(colStr); MessageList::Util::deleteConfig(colStr); } void KMKernel::slotDeleteIdentity(uint identity) { TemplateParser::Util::deleteTemplate(QStringLiteral("IDENTITY_%1").arg(identity)); } bool KMKernel::showPopupAfterDnD() { return KMailSettings::self()->showPopupAfterDnD(); } bool KMKernel::excludeImportantMailFromExpiry() { return KMailSettings::self()->excludeImportantMailFromExpiry(); } qreal KMKernel::closeToQuotaThreshold() { return KMailSettings::self()->closeToQuotaThreshold(); } Akonadi::Collection::Id KMKernel::lastSelectedFolder() { return KMailSettings::self()->lastSelectedFolder(); } void KMKernel::setLastSelectedFolder(Akonadi::Collection::Id col) { KMailSettings::self()->setLastSelectedFolder(col); } QStringList KMKernel::customTemplates() { return GlobalSettingsBase::self()->customTemplates(); } void KMKernel::openFilterDialog(bool createDummyFilter) { if (!mFilterEditDialog) { mFilterEditDialog = new MailCommon::KMFilterDialog(getKMMainWidget()->actionCollections(), nullptr, createDummyFilter); mFilterEditDialog->setObjectName(QStringLiteral("filterdialog")); } mFilterEditDialog->show(); mFilterEditDialog->raise(); mFilterEditDialog->activateWindow(); } void KMKernel::createFilter(const QByteArray &field, const QString &value) { mFilterEditDialog->createFilter(field, value); } void KMKernel::checkFolderFromResources(const Akonadi::Collection::List &collectionList) { const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); for (const Akonadi::AgentInstance &type : lst) { if (type.status() == Akonadi::AgentInstance::Broken) { continue; } const QString typeIdentifier(type.identifier()); if (PimCommon::Util::isImapResource(typeIdentifier)) { OrgKdeAkonadiImapSettingsInterface *iface = PimCommon::Util::createImapSettingsInterface(typeIdentifier); if (iface && iface->isValid()) { const Akonadi::Collection::Id imapTrashId = iface->trashCollection(); for (const Akonadi::Collection &collection : collectionList) { const Akonadi::Collection::Id collectionId = collection.id(); if (imapTrashId == collectionId) { //Use default trash iface->setTrashCollection(CommonKernel->trashCollectionFolder().id()); iface->save(); break; } } } delete iface; } else if (typeIdentifier.contains(POP3_RESOURCE_IDENTIFIER)) { OrgKdeAkonadiPOP3SettingsInterface *iface = MailCommon::Util::createPop3SettingsInterface(typeIdentifier); if (iface->isValid()) { for (const Akonadi::Collection &collection : qAsConst(collectionList)) { const Akonadi::Collection::Id collectionId = collection.id(); if (iface->targetCollection() == collectionId) { //Use default inbox iface->setTargetCollection(CommonKernel->inboxCollectionFolder().id()); iface->save(); break; } } } delete iface; } } } const QAbstractItemModel *KMKernel::treeviewModelSelection() { if (getKMMainWidget()) { return getKMMainWidget()->folderTreeView()->selectionModel()->model(); } else { return entityTreeModel(); } } void KMKernel::slotInstanceWarning(const Akonadi::AgentInstance &instance, const QString &message) { const QString summary = i18nc(": ", "%1: %2", instance.name(), message); KNotification::event(QStringLiteral("akonadi-instance-warning"), summary, QPixmap(), nullptr, KNotification::CloseOnTimeout); } void KMKernel::slotInstanceError(const Akonadi::AgentInstance &instance, const QString &message) { const QString summary = i18nc(": ", "%1: %2", instance.name(), message); KNotification::event(QStringLiteral("akonadi-instance-error"), summary, QPixmap(), nullptr, KNotification::CloseOnTimeout); } void KMKernel::slotInstanceRemoved(const Akonadi::AgentInstance &instance) { const QString identifier(instance.identifier()); const QString resourceGroup = QStringLiteral("Resource %1").arg(identifier); if (KMKernel::config()->hasGroup(resourceGroup)) { KConfigGroup group(KMKernel::config(), resourceGroup); group.deleteGroup(); group.sync(); } if (mResourceCryptoSettingCache.contains(identifier)) { mResourceCryptoSettingCache.remove(identifier); } mFolderArchiveManager->slotInstanceRemoved(instance); } void KMKernel::savePaneSelection() { KMMainWidget *widget = getKMMainWidget(); if (widget) { widget->savePaneSelection(); } } void KMKernel::updatePaneTagComboBox() { KMMainWidget *widget = getKMMainWidget(); if (widget) { widget->updatePaneTagComboBox(); } } void KMKernel::resourceGoOnLine() { KMMainWidget *widget = getKMMainWidget(); if (widget) { if (widget->currentCollection().isValid()) { Akonadi::Collection collection = widget->currentCollection(); Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource()); instance.setIsOnline(true); widget->refreshMessageListSelection(); } } } void KMKernel::makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode) { switch (mode) { case MessageViewer::Viewer::AllResources: resumeNetworkJobs(); break; case MessageViewer::Viewer::SelectedResource: resourceGoOnLine(); break; } } PimCommon::AutoCorrection *KMKernel::composerAutoCorrection() { return mAutoCorrection; } void KMKernel::toggleSystemTray() { KMMainWidget *widget = getKMMainWidget(); if (widget) { if (!mSystemTray && KMailSettings::self()->systemTrayEnabled()) { mSystemTray = new KMail::KMSystemTray(widget); } else if (mSystemTray && !KMailSettings::self()->systemTrayEnabled()) { // Get rid of system tray on user's request qCDebug(KMAIL_LOG) << "deleting systray"; delete mSystemTray; mSystemTray = nullptr; } } } void KMKernel::showFolder(const QString &collectionId) { if (!collectionId.isEmpty()) { const Akonadi::Collection::Id id = collectionId.toLongLong(); selectCollectionFromId(id); } } void KMKernel::reloadFolderArchiveConfig() { mFolderArchiveManager->reloadConfig(); } void KMKernel::slotCollectionChanged(const Akonadi::Collection &, const QSet &set) { if (set.contains("newmailnotifierattribute")) { if (mSystemTray) { mSystemTray->updateSystemTray(); } } } FolderArchiveManager *KMKernel::folderArchiveManager() const { return mFolderArchiveManager; } -bool KMKernel::allowToDebugBalooSupport() const +bool KMKernel::allowToDebugAkonadiSeachSupport() const { - return mDebugBaloo; + return mDebugAkonadiSearch; } bool KMKernel::firstStart() const { return the_firstStart; } QString KMKernel::previousVersion() const { return the_previousVersion; } bool KMKernel::shuttingDown() const { return the_shuttingDown; } void KMKernel::setShuttingDown(bool flag) { the_shuttingDown = flag; } void KMKernel::expunge(Akonadi::Collection::Id col, bool sync) { Q_UNUSED(col); Q_UNUSED(sync); } diff --git a/src/kmkernel.h b/src/kmkernel.h index a9a4ddfc9..175142843 100644 --- a/src/kmkernel.h +++ b/src/kmkernel.h @@ -1,544 +1,544 @@ // #ifndef _KMKERNEL_H #define _KMKERNEL_H #include "mailcommon/mailinterfaces.h" #include #include #include #include #include #include "kmail_export.h" #include "settings/kmailsettings.h" #include #include "messageviewer/viewer.h" #include "libkdepim/progressmanager.h" #define kmkernel KMKernel::self() #define kmconfig KMKernel::config() class QAbstractItemModel; namespace Akonadi { class Collection; class ChangeRecorder; class EntityTreeModel; class EntityMimeTypeFilterModel; } namespace Akonadi { namespace Search { namespace PIM { class IndexedItems; } } } namespace KIO { class Job; } namespace MessageComposer { class MessageSender; } namespace PimCommon { class AutoCorrection; } /** The KMail namespace contains classes used for KMail. * This is to keep them out of the way from all the other * un-namespaced classes in libs and the rest of PIM. */ namespace KMail { class MailServiceImpl; class UndoStack; class KMSystemTray; } using KMail::MailServiceImpl; using KMail::UndoStack; namespace MessageComposer { class AkonadiSender; } namespace KIdentityManagement { class Identity; class IdentityManager; } namespace MailCommon { class Kernel; class FolderSettings; class FolderCollectionMonitor; class JobScheduler; class KMFilterDialog; class MailCommonSettings; } class QTimer; class KMainWindow; class KMMainWidget; class ConfigureDialog; class FolderArchiveManager; class CheckIndexingManager; /** * @short Central point of coordination in KMail * * The KMKernel class represents the core of KMail, where the different parts * come together and are coordinated. It is currently also the class which exports * KMail's main D-BUS interfaces. * * The kernel is responsible for creating various * (singleton) objects such as the identity manager and the message sender. * * The kernel also creates an Akonadi Session, Monitor and EntityTreeModel. These * are shared so that other objects in KMail have access to it. Having only one EntityTreeModel * instead of many reduces the overall communication with the Akonadi server. * * The kernel also manages some stuff that should be factored out: * - default collection handling, like inboxCollectionFolder() * - job handling, like jobScheduler() * - handling of some config settings, like wrapCol() * - various other stuff */ class KMAIL_EXPORT KMKernel : public QObject, public MailCommon::IKernel, public MailCommon::ISettings, public MailCommon::IFilter { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kmail.kmail") public: explicit KMKernel(QObject *parent = nullptr); ~KMKernel(); /** * Start of D-Bus callable stuff. The D-Bus methods need to be public slots, * otherwise they can't be accessed. */ public Q_SLOTS: Q_SCRIPTABLE void checkMail(); Q_SCRIPTABLE void openReader(); /** * Enables/disables systray icon changing when mail arrives. * With this disabled the systray icon will always be the same. */ Q_SCRIPTABLE void setSystrayIconNotificationsEnabled(bool enabled); /** * Pauses all background jobs and does not * allow new background jobs to be started. */ Q_SCRIPTABLE void pauseBackgroundJobs(); /** * Resumes all background jobs and allows * new jobs to be started. */ Q_SCRIPTABLE void resumeBackgroundJobs(); /** * Stops all network related jobs and enter offline mode * New network jobs cannot be started. */ Q_SCRIPTABLE void stopNetworkJobs(); /** * Resumes all network related jobs and enter online mode * New network jobs can be started. */ Q_SCRIPTABLE void resumeNetworkJobs(); Q_SCRIPTABLE QStringList accounts(); Q_SCRIPTABLE void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode); /** * Checks the account with the specified name for new mail. * If the account name is empty, all accounts not excluded from manual * mail check will be checked. */ Q_SCRIPTABLE void checkAccount(const QString &account); Q_SCRIPTABLE bool selectFolder(const QString &folder); Q_SCRIPTABLE bool canQueryClose(); Q_SCRIPTABLE bool handleCommandLine(bool noArgsOpensReader, const QStringList &args, const QString &workingDir); /** * Opens a composer window and prefills it with different * message parts. * * * @param to A comma separated list of To addresses. * @param cc A comma separated list of CC addresses. * @param bcc A comma separated list of BCC addresses. * @param subject The message subject. * @param body The message body. * @param hidden Whether the composer window shall initially be hidden. * @param messageFile A message file that will be used as message body. * @param attachmentPaths A list of files that will be attached to the message. * @param customHeaders A list of custom headers. */ Q_SCRIPTABLE void openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, bool hidden, const QString &messageFile, const QStringList &attachmentPaths, const QStringList &customHeaders, const QString &replyTo = QString(), const QString &inReplyTo = QString(), const QString &identity = QString()); /** * Opens a composer window and prefills it with different * message parts. * * @param to A comma separated list of To addresses. * @param cc A comma separated list of CC addresses. * @param bcc A comma separated list of BCC addresses. * @param subject The message subject. * @param body The message body. * @param hidden Whether the composer window shall initially be hidden. * @param attachName The name of the attachment. * @param attachCte The content transfer encoding of the attachment. * @param attachData The raw data of the attachment. * @param attachType The mime type of the attachment. * @param attachSubType The sub mime type of the attachment. * @param attachParamAttr The parameter attribute of the attachment. * @param attachParamValue The parameter value of the attachment. * @param attachContDisp The content display type of the attachment. * @param attachCharset The charset of the attachment. * @param identity The identity identifier which will be used as sender identity. */ Q_SCRIPTABLE void openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, bool hidden, const QString &attachName, const QByteArray &attachCte, const QByteArray &attachData, const QByteArray &attachType, const QByteArray &attachSubType, const QByteArray &attachParamAttr, const QString &attachParamValue, const QByteArray &attachContDisp, const QByteArray &attachCharset, unsigned int identity); /** * Opens a composer window and prefills it with different * message parts. * @since 5.0 * * @param to A comma separated list of To addresses. * @param cc A comma separated list of CC addresses. * @param bcc A comma separated list of BCC addresses. * @param subject The message subject. * @param body The message body. * @param attachName The name of the attachment. * @param attachCte The content transfer encoding of the attachment. * @param attachData The raw data of the attachment. * @param attachType The mime type of the attachment. * @param attachSubType The sub mime type of the attachment. * @param attachParamAttr The parameter attribute of the attachment. * @param attachParamValue The parameter value of the attachment. * @param attachContDisp The content display type of the attachment. * @param attachCharset The charset of the attachment. * @param identity The identity identifier which will be used as sender identity. */ Q_SCRIPTABLE void openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QString &attachName, const QByteArray &attachCte, const QByteArray &attachData, const QByteArray &attachType, const QByteArray &attachSubType, const QByteArray &attachParamAttr, const QString &attachParamValue, const QByteArray &attachContDisp, const QByteArray &attachCharset, unsigned int identity); /** * Opens a composer window and prefills it with different * message parts. * * * @param to A comma separated list of To addresses. * @param cc A comma separated list of CC addresses. * @param bcc A comma separated list of BCC addresses. * @param subject The message subject. * @param body The message body. * @param hidden Whether the composer window shall initially be hidden. */ Q_SCRIPTABLE void openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, bool hidden); /** * Opens a composer window and prefills it with different * message parts. * * @returns The DBus object path for the composer. * * @param to A comma separated list of To addresses. * @param cc A comma separated list of CC addresses. * @param bcc A comma separated list of BCC addresses. * @param subject The message subject. * @param body The message body. * @param hidden Whether the composer window shall initially be hidden. * @param useFolderId The id of the folder whose associated identity will be used. * @param messageFile A message file that will be used as message body. * @param attachURL The URL to the file that will be attached to the message. */ Q_SCRIPTABLE void newMessage(const QString &to, const QString &cc, const QString &bcc, bool hidden, bool useFolderId, const QString &messageFile, const QString &attachURL); Q_SCRIPTABLE bool showMail(qint64 serialNumber); Q_SCRIPTABLE int viewMessage(const QString &messageFile); Q_SCRIPTABLE void updateConfig(); Q_SCRIPTABLE void showFolder(const QString &collectionId); Q_SCRIPTABLE void reloadFolderArchiveConfig(); /** * End of D-Bus callable stuff */ public: void checkMailOnStartup(); /** A static helper function that asks the user * if they want to go online. * @return true if the user wants to go online * @return false if the user wants to stay offline */ static bool askToGoOnline(); /** Checks if the current network state is online or offline * @return true if the network state is offline * @return false if the network state is online */ static bool isOffline(); /** normal control stuff */ static KMKernel *self(); KSharedConfig::Ptr config() override; void syncConfig() override; void init(); void setupDBus(); void expunge(Akonadi::Collection::Id col, bool sync) override; Akonadi::ChangeRecorder *folderCollectionMonitor() const override; /** * Returns the main model, which contains all folders and the items of recently opened folders. */ Akonadi::EntityTreeModel *entityTreeModel() const; /** * Returns a model of all folders in KMail. This is basically the same as entityTreeModel(), * but with items filtered out, the model contains only collections. */ Akonadi::EntityMimeTypeFilterModel *collectionModel() const override; void recoverDeadLetters(); void closeAllKMailWindows(); void cleanup(void); void quit(); bool doSessionManagement(); bool firstInstance() const; void setFirstInstance(bool value); void action(bool mailto, bool check, const QString &to, const QString &cc, const QString &bcc, const QString &subj, const QString &body, const QUrl &messageFile, const QList &attach, const QStringList &customHeaders, const QString &replyTo, const QString &inReplyTo, const QString &identity); //sets online status for akonadi accounts. true for online, false for offline void setAccountStatus(bool); const QString xmlGuiInstanceName() const; void setXmlGuiInstanceName(const QString &instance); UndoStack *undoStack() const; MessageComposer::MessageSender *msgSender() override; void openFilterDialog(bool createDummyFilter = true) override; void createFilter(const QByteArray &field, const QString &value) override; /** return the pointer to the identity manager */ KIdentityManagement::IdentityManager *identityManager() override; MailCommon::JobScheduler *jobScheduler() const override { return mJobScheduler; } /** Expire all folders, used for the gui action */ void expireAllFoldersNow(); bool firstStart() const; QString previousVersion() const; bool shuttingDown() const; void setShuttingDown(bool flag); /** Returns true if we have a system tray applet. This is needed in order * to know whether the application should be allowed to exit in case the * last visible composer or separate message window is closed. */ bool haveSystemTrayApplet() const; QTextCodec *networkCodec() const; /** returns a reference to the first Mainwin or a temporary Mainwin */ KMainWindow *mainWin(); /** Get first mainwidget */ KMMainWidget *getKMMainWidget(); /** * Returns a list of all currently loaded folders. Since folders are loaded async, this * is empty at startup. */ Akonadi::Collection::List allFolders() const; /** * Includes all subfolders of @p col, including the @p col itself. */ Akonadi::Collection::List subfolders(const Akonadi::Collection &col) const; // void selectCollectionFromId(Akonadi::Collection::Id id); void raise(); void stopAgentInstance(); //ISettings bool showPopupAfterDnD() override; bool excludeImportantMailFromExpiry() override; qreal closeToQuotaThreshold() override; Akonadi::Collection::Id lastSelectedFolder() override; void setLastSelectedFolder(Akonadi::Collection::Id col) override; QStringList customTemplates() override; void checkFolderFromResources(const Akonadi::Collection::List &collectionList); const QAbstractItemModel *treeviewModelSelection(); void savePaneSelection(); void updatePaneTagComboBox(); PimCommon::AutoCorrection *composerAutoCorrection(); void toggleSystemTray(); FolderArchiveManager *folderArchiveManager() const; - bool allowToDebugBalooSupport() const; + bool allowToDebugAkonadiSeachSupport() const; Akonadi::Search::PIM::IndexedItems *indexedItems() const; void cleanupTemporaryFiles(); MailCommon::MailCommonSettings *mailCommonSettings() const; protected: void agentInstanceBroken(const Akonadi::AgentInstance &instance); public Q_SLOTS: void updateSystemTray() override; /** Custom templates have changed, so all windows using them need to regenerate their menus */ void updatedTemplates(); /// Save contents of all open composer widnows to ~/dead.letter void dumpDeadLetters(); /** Call this slot instead of directly KConfig::sync() to minimize the overall config writes. Calling this slot will schedule a sync of the application config file using a timer, so that many consecutive calls can be condensed into a single sync, which is more efficient. */ void slotRequestConfigSync(); /** * Sync the config immediatley */ void slotSyncConfig(); void slotShowConfigurationDialog(); void slotRunBackgroundTasks(); void slotConfigChanged(); Q_SIGNALS: void configChanged(); void onlineStatusChanged(KMailSettings::EnumNetworkState::type); void customTemplatesChanged(); void startCheckMail(); void endCheckMail(); private Q_SLOTS: /** Updates identities when a transport has been deleted. */ void transportRemoved(int id, const QString &name); /** Updates identities when a transport has been renamed. */ void transportRenamed(int id, const QString &oldName, const QString &newName); void itemDispatchStarted(); void instanceStatusChanged(const Akonadi::AgentInstance &); void akonadiStateChanged(Akonadi::ServerManager::State); void slotProgressItemCompletedOrCanceled(KPIM::ProgressItem *item); void slotInstanceError(const Akonadi::AgentInstance &instance, const QString &message); void slotInstanceWarning(const Akonadi::AgentInstance &instance, const QString &message); void slotCollectionRemoved(const Akonadi::Collection &col); void slotDeleteIdentity(uint identity); void slotInstanceRemoved(const Akonadi::AgentInstance &); void slotSystemNetworkStatusChanged(bool isOnline); void slotCollectionChanged(const Akonadi::Collection &, const QSet &set); void slotCheckAccount(Akonadi::ServerManager::State state); private: void viewMessage(const QUrl &url); Akonadi::Collection currentCollection(); /* * Fills a composer cWin * */ void fillComposer(bool hidden, const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QString &attachName, const QByteArray &attachCte, const QByteArray &attachData, const QByteArray &attachType, const QByteArray &attachSubType, const QByteArray &attachParamAttr, const QString &attachParamValue, const QByteArray &attachContDisp, const QByteArray &attachCharset, unsigned int identity, bool forceShowWindow); void verifyAccount(); void resourceGoOnLine(); void openReader(bool onlyCheck); QSharedPointer currentFolderCollection(); UndoStack *the_undoStack = nullptr; MessageComposer::AkonadiSender *the_msgSender = nullptr; /** previous KMail version. If different from current, the user has just updated. read from config */ QString the_previousVersion; /** is this the first start? read from config */ bool the_firstStart; /** are we going down? set from here */ bool the_shuttingDown; /** true unles kmail is closed by session management */ bool the_firstInstance; KSharedConfig::Ptr mConfig; QTextCodec *mNetCodec = nullptr; QString mXmlGuiInstance; ConfigureDialog *mConfigureDialog = nullptr; QTimer *mBackgroundTasksTimer = nullptr; MailCommon::JobScheduler *mJobScheduler = nullptr; MailServiceImpl *mMailService = nullptr; bool mSystemNetworkStatus; KMail::KMSystemTray *mSystemTray = nullptr; QHash mResourceCryptoSettingCache; MailCommon::FolderCollectionMonitor *mFolderCollectionMonitor = nullptr; Akonadi::EntityTreeModel *mEntityTreeModel = nullptr; Akonadi::EntityMimeTypeFilterModel *mCollectionModel = nullptr; /// List of Akonadi resources that are currently being checked. QStringList mResourcesBeingChecked; QPointer mFilterEditDialog; PimCommon::AutoCorrection *mAutoCorrection = nullptr; FolderArchiveManager *mFolderArchiveManager = nullptr; CheckIndexingManager *mCheckIndexingManager = nullptr; Akonadi::Search::PIM::IndexedItems *mIndexedItems = nullptr; MailCommon::MailCommonSettings *mMailCommonSettings = nullptr; - bool mDebugBaloo = false; + bool mDebugAkonadiSearch = false; }; #endif // _KMKERNEL_H diff --git a/src/kmmainwidget.cpp b/src/kmmainwidget.cpp index 4451a900f..188d56b91 100644 --- a/src/kmmainwidget.cpp +++ b/src/kmmainwidget.cpp @@ -1,4759 +1,4759 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2009-2017 Montel Laurent Based on the work of Stefan Taferner KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // KMail includes #include "kmreadermainwin.h" #include "job/composenewmessagejob.h" #include "editor/composer.h" #include "searchdialog/searchwindow.h" #include "widgets/vacationscriptindicatorwidget.h" #include "undostack.h" #include "kmcommands.h" #include "kmmainwin.h" #include "kmsystemtray.h" #include #include "MailCommon/FolderSelectionDialog" #include "MailCommon/FolderTreeWidget" #include "PimCommonAkonadi/MailUtil" #include "util.h" #include "mailcommon/mailutil.h" #include "mailcommon/mailkernel.h" #include "dialog/archivefolderdialog.h" #include "settings/kmailsettings.h" #include "MailCommon/FolderTreeView" #include "tag/tagactionmanager.h" #include "foldershortcutactionmanager.h" #include "widgets/collectionpane.h" #include "manageshowcollectionproperties.h" #include "widgets/kactionmenutransport.h" #include "widgets/kactionmenuaccount.h" #include "mailcommon/searchrulestatus.h" #include "plugininterface/kmailplugininterface.h" #include "PimCommon/NetworkUtil" #include "kpimtextedit/texttospeech.h" #include "job/markallmessagesasreadinfolderandsubfolderjob.h" #include "job/removeduplicatemessageinfolderandsubfolderjob.h" #include "sieveimapinterface/kmsieveimappasswordprovider.h" #if !defined(NDEBUG) #include using KSieveUi::SieveDebugDialog; #endif #include #include "collectionpage/collectionquotapage.h" #include "collectionpage/collectiontemplatespage.h" #include "collectionpage/collectionshortcutpage.h" #include "collectionpage/collectionviewpage.h" #include "collectionpage/collectionmailinglistpage.h" #include "tag/tagselectdialog.h" #include "job/createnewcontactjob.h" #include "folderarchive/folderarchiveutil.h" #include "folderarchive/folderarchivemanager.h" #include #include "PimCommon/PimUtil" #include "MailCommon/CollectionGeneralPage" #include "MailCommon/CollectionExpiryPage" #include "MailCommon/ExpireCollectionAttribute" #include "MailCommon/FilterManager" #include "MailCommon/MailFilter" #include "MailCommon/FavoriteCollectionWidget" #include "MailCommon/FolderTreeWidget" #include "MailCommon/FolderTreeView" #include "mailcommonsettings_base.h" #include "kmmainwidget.h" // Other PIM includes #include "kmail-version.h" #include "messageviewer/messageviewersettings.h" #include "messageviewer/viewer.h" #include "messageviewer/headerstyleplugin.h" #include "messageviewer/headerstyle.h" #include #ifndef QT_NO_CURSOR #include "Libkdepim/KCursorSaver" #endif #include #include "MessageComposer/MessageHelper" #include #include "MessageCore/MailingList" #include "dialog/kmknotify.h" #include "widgets/displaymessageformatactionmenu.h" #include "ksieveui/vacationmanager.h" #include "kmlaunchexternalcomponent.h" // LIBKDEPIM includes #include "libkdepim/progressmanager.h" #include "libkdepim/broadcaststatus.h" // KDEPIMLIBS includes #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 #include #include #include #include #include #include // KDELIBS includes #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt includes #include #include #include #include #include #include #include #include // System includes #include #include #include "PimCommonAkonadi/ManageServerSideSubscriptionJob" #include #include using namespace KMime; using namespace Akonadi; using namespace MailCommon; using KPIM::ProgressManager; using KPIM::BroadcastStatus; using KMail::SearchWindow; using KMime::Types::AddrSpecList; using MimeTreeParser::AttachmentStrategy; Q_GLOBAL_STATIC(KMMainWidget::PtrList, theMainWidgetList) //----------------------------------------------------------------------------- KMMainWidget::KMMainWidget(QWidget *parent, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, KSharedConfig::Ptr config) : QWidget(parent) , mMoveMsgToFolderAction(nullptr) , mCollectionProperties(nullptr) , mFavoriteCollectionsView(nullptr) , mMsgView(nullptr) , mSplitter1(nullptr) , mSplitter2(nullptr) , mFolderViewSplitter(nullptr) , mArchiveFolderAction(nullptr) , mShowBusySplashTimer(nullptr) , mMsgActions(nullptr) , mCurrentFolderSettings(nullptr) , mVacationIndicatorActive(false) , mGoToFirstUnreadMessageInSelectedFolder(false) , mDisplayMessageFormatMenu(nullptr) , mFolderDisplayFormatPreference(MessageViewer::Viewer::UseGlobalSetting) , mSearchMessages(nullptr) , mManageShowCollectionProperties(new ManageShowCollectionProperties(this, this)) , mShowIntroductionAction(nullptr) , mMarkAllMessageAsReadAndInAllSubFolder(nullptr) , mAccountActionMenu(nullptr) , mRemoveDuplicateRecursiveAction(nullptr) { mLaunchExternalComponent = new KMLaunchExternalComponent(this, this); // must be the first line of the constructor: mStartupDone = false; mWasEverShown = false; mReaderWindowActive = true; mReaderWindowBelow = true; mFolderHtmlLoadExtPreference = false; mDestructed = false; mActionCollection = actionCollection; mTopLayout = new QVBoxLayout(this); mTopLayout->setMargin(0); mConfig = config; mGUIClient = aGUIClient; mFolderTreeWidget = nullptr; mPreferHtmlLoadExtAction = nullptr; Akonadi::ControlGui::widgetNeedsAkonadi(this); mFavoritesModel = nullptr; mSievePasswordProvider = new KMSieveImapPasswordProvider(winId()); mVacationManager = new KSieveUi::VacationManager(mSievePasswordProvider, this); connect(mVacationManager, SIGNAL(updateVacationScriptStatus(bool,QString)), SLOT(updateVacationScriptStatus(bool,QString))); mToolbarActionSeparator = new QAction(this); mToolbarActionSeparator->setSeparator(true); KMailPluginInterface::self()->setActionCollection(mActionCollection); KMailPluginInterface::self()->initializePlugins(); KMailPluginInterface::self()->setMainWidget(this); theMainWidgetList->append(this); readPreConfig(); createWidgets(); setupActions(); readConfig(); if (!kmkernel->isOffline()) { //kmail is set to online mode, make sure the agents are also online kmkernel->setAccountStatus(true); } QTimer::singleShot(0, this, &KMMainWidget::slotShowStartupFolder); connect(kmkernel, &KMKernel::startCheckMail, this, &KMMainWidget::slotStartCheckMail); connect(kmkernel, &KMKernel::endCheckMail, this, &KMMainWidget::slotEndCheckMail); connect(kmkernel, &KMKernel::configChanged, this, &KMMainWidget::slotConfigChanged); connect(kmkernel, &KMKernel::onlineStatusChanged, this, &KMMainWidget::slotUpdateOnlineStatus); connect(mTagActionManager, &KMail::TagActionManager::tagActionTriggered, this, &KMMainWidget::slotUpdateMessageTagList); connect(mTagActionManager, &KMail::TagActionManager::tagMoreActionClicked, this, &KMMainWidget::slotSelectMoreMessageTagList); kmkernel->toggleSystemTray(); { // make sure the pages are registered only once, since there can be multiple instances of KMMainWidget static bool pagesRegistered = false; if (!pagesRegistered) { Akonadi::CollectionPropertiesDialog::registerPage(new PimCommon::CollectionAclPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new MailCommon::CollectionGeneralPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new CollectionMaintenancePageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new CollectionQuotaPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new CollectionTemplatesPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new MailCommon::CollectionExpiryPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new CollectionViewPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new CollectionMailingListPageFactory); Akonadi::CollectionPropertiesDialog::registerPage(new CollectionShortcutPageFactory); pagesRegistered = true; } } KMainWindow *mainWin = qobject_cast(window()); QStatusBar *sb = mainWin ? mainWin->statusBar() : nullptr; mVacationScriptIndicator = new KMail::VacationScriptIndicatorWidget(sb); mVacationScriptIndicator->hide(); connect(mVacationScriptIndicator, &KMail::VacationScriptIndicatorWidget::clicked, this, &KMMainWidget::slotEditVacation); if (KSieveUi::Util::checkOutOfOfficeOnStartup()) { QTimer::singleShot(0, this, &KMMainWidget::slotCheckVacation); } connect(mFolderTreeWidget->folderTreeView()->model(), &QAbstractItemModel::modelReset, this, &KMMainWidget::restoreCollectionFolderViewConfig); restoreCollectionFolderViewConfig(); if (kmkernel->firstStart()) { const QStringList listOfMailerFound = MailCommon::Util::foundMailer(); if (!listOfMailerFound.isEmpty()) { if (KMessageBox::questionYesNoList(this, i18n("Another mailer was found on system. Do you want to import data from it?"), listOfMailerFound) == KMessageBox::Yes) { const QString path = QStandardPaths::findExecutable(QStringLiteral("akonadiimportwizard")); if (!QProcess::startDetached(path)) { KMessageBox::error(this, i18n("Could not start the import wizard. " "Please check your installation."), i18n("Unable to start import wizard")); } } else { mLaunchExternalComponent->slotAccountWizard(); } } else { mLaunchExternalComponent->slotAccountWizard(); } } // must be the last line of the constructor: mStartupDone = true; mCheckMailTimer.setInterval(3 * 1000); mCheckMailTimer.setSingleShot(true); connect(&mCheckMailTimer, &QTimer::timeout, this, &KMMainWidget::slotUpdateActionsAfterMailChecking); } void KMMainWidget::restoreCollectionFolderViewConfig() { ETMViewStateSaver *saver = new ETMViewStateSaver; saver->setView(mFolderTreeWidget->folderTreeView()); const KConfigGroup cfg(KMKernel::self()->config(), "CollectionFolderView"); mFolderTreeWidget->restoreHeaderState(cfg.readEntry("HeaderState", QByteArray())); saver->restoreState(cfg); //Restore startup folder Akonadi::Collection::Id id = -1; if (mCurrentCollection.isValid()) { id = mCurrentCollection.id(); } if (id == -1) { if (KMailSettings::self()->startSpecificFolderAtStartup()) { Akonadi::Collection::Id startupFolder = KMailSettings::self()->startupFolder(); if (startupFolder > 0) { saver->restoreCurrentItem(QStringLiteral("c%1").arg(startupFolder)); } } } else { saver->restoreCurrentItem(QStringLiteral("c%1").arg(id)); } } //----------------------------------------------------------------------------- //The kernel may have already been deleted when this method is called, //perform all cleanup that requires the kernel in destruct() KMMainWidget::~KMMainWidget() { theMainWidgetList->removeAll(this); qDeleteAll(mFilterCommands); destruct(); } //----------------------------------------------------------------------------- //This method performs all cleanup that requires the kernel to exist. void KMMainWidget::destruct() { if (mDestructed) { return; } if (mSearchWin) { mSearchWin->close(); } disconnect(mFolderTreeWidget->folderTreeView()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KMMainWidget::updateFolderMenu); writeConfig(false); /* don't force kmkernel sync when close BUG: 289287 */ writeFolderConfig(); deleteWidgets(); clearCurrentFolder(); delete mMoveOrCopyToDialog; delete mSelectFromAllFoldersDialog; delete mSievePasswordProvider; disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), this, nullptr); disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(itemRemoved(Akonadi::Item)), this, nullptr); disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), this, nullptr); disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(collectionChanged(Akonadi::Collection,QSet)), this, nullptr); disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), this, nullptr); mDestructed = true; } void KMMainWidget::clearCurrentFolder() { mCurrentFolderSettings.clear(); mCurrentCollection = Akonadi::Collection(); } void KMMainWidget::slotStartCheckMail() { if (mCheckMailTimer.isActive()) { mCheckMailTimer.stop(); } } void KMMainWidget::slotEndCheckMail() { if (!mCheckMailTimer.isActive()) { mCheckMailTimer.start(); } } void KMMainWidget::slotUpdateActionsAfterMailChecking() { const bool sendOnAll = KMailSettings::self()->sendOnCheck() == KMailSettings::EnumSendOnCheck::SendOnAllChecks; const bool sendOnManual = KMailSettings::self()->sendOnCheck() == KMailSettings::EnumSendOnCheck::SendOnManualChecks; if (!kmkernel->isOffline() && (sendOnAll || sendOnManual)) { slotSendQueued(); } // update folder menus in case some mail got filtered to trash/current folder // and we can enable "empty trash/move all to trash" action etc. updateFolderMenu(); } void KMMainWidget::setCurrentCollection(const Akonadi::Collection &col) { mCurrentCollection = col; if (mCurrentFolderSettings) { mCurrentFolderSettings->setCollection(col); } } void KMMainWidget::slotCollectionFetched(int collectionId) { // Called when a collection is fetched for the first time by the ETM. // This is the right time to update the caption (which still says "Loading...") // and to update the actions that depend on the number of mails in the folder. if (collectionId == mCurrentCollection.id()) { setCurrentCollection(CommonKernel->collectionFromId(mCurrentCollection.id())); updateMessageActions(); updateFolderMenu(); } // We call this for any collection, it could be one of our parents... if (mCurrentCollection.isValid()) { Q_EMIT captionChangeRequest(fullCollectionPath()); } } QString KMMainWidget::fullCollectionPath() const { if (mCurrentCollection.isValid()) { return MailCommon::Util::fullCollectionPath(mCurrentCollection); } return {}; } void KMMainWidget::slotFolderChanged(const Akonadi::Collection &collection) { folderSelected(collection); if (collection.cachePolicy().syncOnDemand()) { AgentManager::self()->synchronizeCollection(collection, false); } mMsgActions->setCurrentMessage(Akonadi::Item()); Q_EMIT captionChangeRequest(MailCommon::Util::fullCollectionPath(collection)); } void KMMainWidget::folderSelected(const Akonadi::Collection &col) { // This is connected to the MainFolderView signal triggering when a folder is selected if (mGoToFirstUnreadMessageInSelectedFolder) { // the default action has been overridden from outside mPreSelectionMode = MessageList::Core::PreSelectFirstUnreadCentered; } else { // use the default action switch (KMailSettings::self()->actionEnterFolder()) { case KMailSettings::EnumActionEnterFolder::SelectFirstUnread: mPreSelectionMode = MessageList::Core::PreSelectFirstUnreadCentered; break; case KMailSettings::EnumActionEnterFolder::SelectLastSelected: mPreSelectionMode = MessageList::Core::PreSelectLastSelected; break; case KMailSettings::EnumActionEnterFolder::SelectNewest: mPreSelectionMode = MessageList::Core::PreSelectNewestCentered; break; case KMailSettings::EnumActionEnterFolder::SelectOldest: mPreSelectionMode = MessageList::Core::PreSelectOldestCentered; break; default: mPreSelectionMode = MessageList::Core::PreSelectNone; break; } } mGoToFirstUnreadMessageInSelectedFolder = false; #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif if (mMsgView) { mMsgView->clear(true); } const bool newFolder = mCurrentCollection != col; // Delete any pending timer, if needed it will be recreated below delete mShowBusySplashTimer; mShowBusySplashTimer = nullptr; if (newFolder) { // We're changing folder: write configuration for the old one writeFolderConfig(); } mCurrentFolderSettings = FolderSettings::forCollection(col); mCurrentCollection = col; readFolderConfig(); if (mMsgView) { mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference); mMsgView->setHtmlLoadExtOverride(mFolderHtmlLoadExtPreference); } if (!mCurrentFolderSettings->isValid() && (mMessagePane->count() < 2)) { slotIntro(); } // The message pane uses the selection model of the folder view to load the correct aggregation model and theme // settings. At this point the selection model hasn't been updated yet to the user's new choice, so it would load // the old folder settings instead. QTimer::singleShot(0, this, &KMMainWidget::slotShowSelectedFolderInPane); } void KMMainWidget::slotShowSelectedFolderInPane() { if (mCurrentCollection.isValid()) { mMessagePane->setCurrentFolder(mCurrentCollection, false, mPreSelectionMode); } updateMessageActions(); updateFolderMenu(); } void KMMainWidget::clearViewer() { if (mMsgView) { mMsgView->clear(true); mMsgView->displayAboutPage(); } } //----------------------------------------------------------------------------- void KMMainWidget::readPreConfig() { mLongFolderList = KMailSettings::self()->folderList() == KMailSettings::EnumFolderList::longlist; mReaderWindowActive = KMailSettings::self()->readerWindowMode() != KMailSettings::EnumReaderWindowMode::hide; mReaderWindowBelow = KMailSettings::self()->readerWindowMode() == KMailSettings::EnumReaderWindowMode::below; mHtmlGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail(); mHtmlLoadExtGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlLoadExternal(); mEnableFavoriteFolderView = (KMKernel::self()->mailCommonSettings()->favoriteCollectionViewMode() != MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::HiddenMode); mEnableFolderQuickSearch = KMailSettings::self()->enableFolderQuickSearch(); readFolderConfig(); updateHtmlMenuEntry(); if (mMsgView) { mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference); mMsgView->update(true); } } //----------------------------------------------------------------------------- void KMMainWidget::readFolderConfig() { if (!mCurrentCollection.isValid()) { return; } KSharedConfig::Ptr config = KMKernel::self()->config(); KConfigGroup group(config, MailCommon::FolderSettings::configGroupName(mCurrentCollection)); if (group.hasKey("htmlMailOverride")) { const bool useHtml = group.readEntry("htmlMailOverride", false); mFolderDisplayFormatPreference = useHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text; group.deleteEntry("htmlMailOverride"); group.sync(); } else { mFolderDisplayFormatPreference = static_cast(group.readEntry("displayFormatOverride", static_cast(MessageViewer::Viewer::UseGlobalSetting))); } mFolderHtmlLoadExtPreference = group.readEntry("htmlLoadExternalOverride", false); } //----------------------------------------------------------------------------- void KMMainWidget::writeFolderConfig() { if (mCurrentCollection.isValid()) { KSharedConfig::Ptr config = KMKernel::self()->config(); KConfigGroup group(config, MailCommon::FolderSettings::configGroupName(mCurrentCollection)); group.writeEntry("htmlLoadExternalOverride", mFolderHtmlLoadExtPreference); if (mFolderDisplayFormatPreference == MessageViewer::Viewer::UseGlobalSetting) { group.deleteEntry("displayFormatOverride"); } else { group.writeEntry("displayFormatOverride", static_cast(mFolderDisplayFormatPreference)); } } } //----------------------------------------------------------------------------- void KMMainWidget::layoutSplitters() { // This function can only be called when the old splitters are already deleted Q_ASSERT(!mSplitter1); Q_ASSERT(!mSplitter2); // For some reason, this is necessary here so that the copy action still // works after changing the folder layout. if (mMsgView) { disconnect(mMsgView->copyAction(), &QAction::triggered, mMsgView, &KMReaderWin::slotCopySelectedText); } // If long folder list is enabled, the splitters are: // Splitter 1: FolderView vs (HeaderAndSearch vs MessageViewer) // Splitter 2: HeaderAndSearch vs MessageViewer // // If long folder list is disabled, the splitters are: // Splitter 1: (FolderView vs HeaderAndSearch) vs MessageViewer // Splitter 2: FolderView vs HeaderAndSearch // The folder view is both the folder tree and the favorite folder view, if // enabled const bool readerWindowAtSide = !mReaderWindowBelow && mReaderWindowActive; const bool readerWindowBelow = mReaderWindowBelow && mReaderWindowActive; mSplitter1 = new QSplitter(this); mSplitter2 = new QSplitter(mSplitter1); QWidget *folderTreeWidget = mFolderTreeWidget; if (mFavoriteCollectionsView) { mFolderViewSplitter = new QSplitter(Qt::Vertical); mFolderViewSplitter->setChildrenCollapsible(false); mFolderViewSplitter->addWidget(mFavoriteCollectionsView); mFolderViewSplitter->addWidget(mFolderTreeWidget); folderTreeWidget = mFolderViewSplitter; } if (mLongFolderList) { // add folder tree mSplitter1->setOrientation(Qt::Horizontal); mSplitter1->addWidget(folderTreeWidget); // and the rest to the right mSplitter1->addWidget(mSplitter2); // add the message list to the right or below if (readerWindowAtSide) { mSplitter2->setOrientation(Qt::Horizontal); } else { mSplitter2->setOrientation(Qt::Vertical); } mSplitter2->addWidget(mMessagePane); // add the preview window, if there is one if (mMsgView) { mSplitter2->addWidget(mMsgView); } } else { // short folder list if (mReaderWindowBelow) { mSplitter1->setOrientation(Qt::Vertical); mSplitter2->setOrientation(Qt::Horizontal); } else { // at side or none mSplitter1->setOrientation(Qt::Horizontal); mSplitter2->setOrientation(Qt::Vertical); } mSplitter1->addWidget(mSplitter2); // add folder tree mSplitter2->addWidget(folderTreeWidget); // add message list to splitter 2 mSplitter2->addWidget(mMessagePane); // add the preview window, if there is one if (mMsgView) { mSplitter1->addWidget(mMsgView); } } // // Set splitter properties // mSplitter1->setObjectName(QStringLiteral("splitter1")); mSplitter1->setChildrenCollapsible(false); mSplitter2->setObjectName(QStringLiteral("splitter2")); mSplitter2->setChildrenCollapsible(false); // // Set the stretch factors // mSplitter1->setStretchFactor(0, 0); mSplitter2->setStretchFactor(0, 0); mSplitter1->setStretchFactor(1, 1); mSplitter2->setStretchFactor(1, 1); if (mFavoriteCollectionsView) { mFolderViewSplitter->setStretchFactor(0, 0); mFolderViewSplitter->setStretchFactor(1, 1); } // Because the reader windows's width increases a tiny bit after each // restart in short folder list mode with message window at side, disable // the stretching as a workaround here if (readerWindowAtSide && !mLongFolderList) { mSplitter1->setStretchFactor(0, 1); mSplitter1->setStretchFactor(1, 0); } // // Set the sizes of the splitters to the values stored in the config // QList splitter1Sizes; QList splitter2Sizes; const int folderViewWidth = KMailSettings::self()->folderViewWidth(); int ftHeight = KMailSettings::self()->folderTreeHeight(); int headerHeight = KMailSettings::self()->searchAndHeaderHeight(); const int messageViewerWidth = KMailSettings::self()->readerWindowWidth(); int headerWidth = KMailSettings::self()->searchAndHeaderWidth(); int messageViewerHeight = KMailSettings::self()->readerWindowHeight(); int ffvHeight = mFolderViewSplitter ? KMKernel::self()->mailCommonSettings()->favoriteCollectionViewHeight() : 0; // If the message viewer was hidden before, make sure it is not zero height if (messageViewerHeight < 10 && readerWindowBelow) { headerHeight /= 2; messageViewerHeight = headerHeight; } if (mLongFolderList) { if (!readerWindowAtSide) { splitter1Sizes << folderViewWidth << headerWidth; splitter2Sizes << headerHeight << messageViewerHeight; } else { splitter1Sizes << folderViewWidth << (headerWidth + messageViewerWidth); splitter2Sizes << headerWidth << messageViewerWidth; } } else { if (!readerWindowAtSide) { splitter1Sizes << headerHeight << messageViewerHeight; splitter2Sizes << folderViewWidth << headerWidth; } else { splitter1Sizes << headerWidth << messageViewerWidth; splitter2Sizes << ftHeight + ffvHeight << messageViewerHeight; } } mSplitter1->setSizes(splitter1Sizes); mSplitter2->setSizes(splitter2Sizes); if (mFolderViewSplitter) { QList splitterSizes; splitterSizes << ffvHeight << ftHeight; mFolderViewSplitter->setSizes(splitterSizes); } // // Now add the splitters to the main layout // mTopLayout->addWidget(mSplitter1); // Make sure the focus is on the view, and not on the quick search line edit, because otherwise // shortcuts like + or j go to the wrong place. // This would normally be done in the message list itself, but apparently something resets the focus // again, probably all the reparenting we do here. mMessagePane->focusView(); // By default hide th unread and size columns on first run. if (kmkernel->firstStart()) { mFolderTreeWidget->folderTreeView()->hideColumn(1); mFolderTreeWidget->folderTreeView()->hideColumn(3); mFolderTreeWidget->folderTreeView()->header()->resizeSection(0, folderViewWidth * 0.8); } // Make the copy action work, see disconnect comment above if (mMsgView) { connect(mMsgView->copyAction(), &QAction::triggered, mMsgView, &KMReaderWin::slotCopySelectedText); } } //----------------------------------------------------------------------------- void KMMainWidget::refreshFavoriteFoldersViewProperties() { if (mFavoriteCollectionsView) { if (KMKernel::self()->mailCommonSettings()->favoriteCollectionViewMode() == MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode) { mFavoriteCollectionsView->changeViewMode(QListView::IconMode); } else if (KMKernel::self()->mailCommonSettings()->favoriteCollectionViewMode() == MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode) { mFavoriteCollectionsView->changeViewMode(QListView::ListMode); } else { Q_ASSERT(false); // we should never get here in hidden mode } mFavoriteCollectionsView->setDropActionMenuEnabled(kmkernel->showPopupAfterDnD()); mFavoriteCollectionsView->setWordWrap(true); mFavoriteCollectionsView->updateMode(); } } //----------------------------------------------------------------------------- void KMMainWidget::readConfig() { const bool oldLongFolderList = mLongFolderList; const bool oldReaderWindowActive = mReaderWindowActive; const bool oldReaderWindowBelow = mReaderWindowBelow; const bool oldFavoriteFolderView = mEnableFavoriteFolderView; const bool oldFolderQuickSearch = mEnableFolderQuickSearch; // on startup, the layout is always new and we need to relayout the widgets bool layoutChanged = !mStartupDone; if (mStartupDone) { readPreConfig(); layoutChanged = (oldLongFolderList != mLongFolderList) || (oldReaderWindowActive != mReaderWindowActive) || (oldReaderWindowBelow != mReaderWindowBelow) || (oldFavoriteFolderView != mEnableFavoriteFolderView); if (layoutChanged) { deleteWidgets(); createWidgets(); restoreCollectionFolderViewConfig(); Q_EMIT recreateGui(); } else if (oldFolderQuickSearch != mEnableFolderQuickSearch) { if (mEnableFolderQuickSearch) { mFolderTreeWidget->filterFolderLineEdit()->show(); } else { mFolderTreeWidget->filterFolderLineEdit()->hide(); } } } { // Read the config of the folder views and the header if (mMsgView) { mMsgView->readConfig(); } mMessagePane->reloadGlobalConfiguration(); mFolderTreeWidget->readConfig(); if (mFavoriteCollectionsView) { mFavoriteCollectionsView->readConfig(); } refreshFavoriteFoldersViewProperties(); } { // area for config group "General" if (!mStartupDone) { // check mail on startup // do it after building the kmmainwin, so that the progressdialog is available QTimer::singleShot(0, this, &KMMainWidget::slotCheckMailOnStartup); } } if (layoutChanged) { layoutSplitters(); } updateMessageMenu(); updateFileMenu(); kmkernel->toggleSystemTray(); mAccountActionMenu->setAccountOrder(KMKernel::self()->mailCommonSettings()->order()); connect(Akonadi::AgentManager::self(), &AgentManager::instanceAdded, this, &KMMainWidget::updateFileMenu); connect(Akonadi::AgentManager::self(), &AgentManager::instanceRemoved, this, &KMMainWidget::updateFileMenu); } //----------------------------------------------------------------------------- void KMMainWidget::writeConfig(bool force) { // Don't save the sizes of all the widgets when we were never shown. // This can happen in Kontact, where the KMail plugin is automatically // loaded, but not necessarily shown. // This prevents invalid sizes from being saved if (mWasEverShown) { // The height of the header widget can be 0, this happens when the user // did not switch to the header widget onced and the "Welcome to KMail" // HTML widget was shown the whole time int headersHeight = mMessagePane->height(); if (headersHeight == 0) { headersHeight = height() / 2; } KMailSettings::self()->setSearchAndHeaderHeight(headersHeight); KMailSettings::self()->setSearchAndHeaderWidth(mMessagePane->width()); if (mFavoriteCollectionsView) { KMKernel::self()->mailCommonSettings()->setFavoriteCollectionViewHeight(mFavoriteCollectionsView->height()); KMailSettings::self()->setFolderTreeHeight(mFolderTreeWidget->height()); if (!mLongFolderList) { KMailSettings::self()->setFolderViewHeight(mFolderViewSplitter->height()); } } else if (!mLongFolderList && mFolderTreeWidget) { KMailSettings::self()->setFolderTreeHeight(mFolderTreeWidget->height()); } if (mFolderTreeWidget) { KMailSettings::self()->setFolderViewWidth(mFolderTreeWidget->width()); KSharedConfig::Ptr config = KMKernel::self()->config(); KConfigGroup group(config, "CollectionFolderView"); ETMViewStateSaver saver; saver.setView(mFolderTreeWidget->folderTreeView()); saver.saveState(group); group.writeEntry("HeaderState", mFolderTreeWidget->folderTreeView()->header()->saveState()); //Work around from startup folder group.deleteEntry("Selection"); #if 0 if (!KMailSettings::self()->startSpecificFolderAtStartup()) { group.deleteEntry("Current"); } #endif group.sync(); } if (mMsgView) { if (!mReaderWindowBelow) { KMailSettings::self()->setReaderWindowWidth(mMsgView->width()); } mMsgView->viewer()->writeConfig(force); KMailSettings::self()->setReaderWindowHeight(mMsgView->height()); } } } void KMMainWidget::writeReaderConfig() { if (mWasEverShown) { if (mMsgView) { mMsgView->viewer()->writeConfig(); } } } KMReaderWin *KMMainWidget::messageView() const { return mMsgView; } CollectionPane *KMMainWidget::messageListPane() const { return mMessagePane; } Collection KMMainWidget::currentCollection() const { return mCurrentCollection; } //----------------------------------------------------------------------------- void KMMainWidget::deleteWidgets() { // Simply delete the top splitter, which always is mSplitter1, regardless // of the layout. This deletes all children. // akonadi action manager is created in createWidgets(), parented to this // so not autocleaned up. delete mAkonadiStandardActionManager; mAkonadiStandardActionManager = nullptr; delete mSplitter1; mMsgView = nullptr; mFolderViewSplitter = nullptr; mFavoriteCollectionsView = nullptr; mFolderTreeWidget = nullptr; mSplitter1 = nullptr; mSplitter2 = nullptr; mFavoritesModel = nullptr; } //----------------------------------------------------------------------------- void KMMainWidget::createWidgets() { // Note that all widgets we create in this function have the parent 'this'. // They will be properly reparented in layoutSplitters() // // Create the folder tree // FolderTreeWidget::TreeViewOptions opt = FolderTreeWidget::ShowUnreadCount; opt |= FolderTreeWidget::UseLineEditForFiltering; opt |= FolderTreeWidget::ShowCollectionStatisticAnimation; opt |= FolderTreeWidget::DontKeyFilter; mFolderTreeWidget = new FolderTreeWidget(this, mGUIClient, opt); connect(mFolderTreeWidget->folderTreeView(), SIGNAL(currentChanged(Akonadi::Collection)), this, SLOT(slotFolderChanged(Akonadi::Collection))); connect(mFolderTreeWidget->folderTreeView()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KMMainWidget::updateFolderMenu); connect(mFolderTreeWidget->folderTreeView(), &FolderTreeView::newTabRequested, this, &KMMainWidget::slotCreateNewTab); // // Create the message pane // mMessagePane = new CollectionPane(!KMailSettings::self()->startSpecificFolderAtStartup(), KMKernel::self()->entityTreeModel(), mFolderTreeWidget->folderTreeView()->selectionModel(), this); connect(KMKernel::self()->entityTreeModel(), &Akonadi::EntityTreeModel::collectionFetched, this, &KMMainWidget::slotCollectionFetched); mMessagePane->setXmlGuiClient(mGUIClient); connect(mMessagePane, &MessageList::Pane::messageSelected, this, &KMMainWidget::slotMessageSelected); connect(mMessagePane, &MessageList::Pane::selectionChanged, this, &KMMainWidget::startUpdateMessageActionsTimer); connect(mMessagePane, &CollectionPane::currentTabChanged, this, &KMMainWidget::refreshMessageListSelection); connect(mMessagePane, &MessageList::Pane::messageActivated, this, &KMMainWidget::slotMessageActivated); connect(mMessagePane, &MessageList::Pane::messageStatusChangeRequest, this, &KMMainWidget::slotMessageStatusChangeRequest); connect(mMessagePane, &MessageList::Pane::statusMessage, this, &KMMainWidget::showMessageActivities); // // Create the reader window // if (mReaderWindowActive) { mMsgView = new KMReaderWin(this, this, actionCollection()); if (mMsgActions) { mMsgActions->setMessageView(mMsgView); } connect(mMsgView->viewer(), &MessageViewer::Viewer::displayPopupMenu, this, &KMMainWidget::slotMessagePopup); connect(mMsgView->viewer(), &MessageViewer::Viewer::moveMessageToTrash, this, &KMMainWidget::slotMoveMessageToTrash); connect(mMsgView->viewer(), &MessageViewer::Viewer::pageIsScrolledToBottom, this, &KMMainWidget::slotPageIsScrolledToBottom); connect(mMsgView->viewer(), &MessageViewer::Viewer::replyMessageTo, this, &KMMainWidget::slotReplyMessageTo); if (mShowIntroductionAction) { mShowIntroductionAction->setEnabled(true); } } else { if (mMsgActions) { mMsgActions->setMessageView(nullptr); } if (mShowIntroductionAction) { mShowIntroductionAction->setEnabled(false); } } if (!KMailSettings::self()->enableFolderQuickSearch()) { mFolderTreeWidget->filterFolderLineEdit()->hide(); } // // Create the favorite folder view // mAkonadiStandardActionManager = new Akonadi::StandardMailActionManager(mGUIClient->actionCollection(), this); connect(mAkonadiStandardActionManager, &Akonadi::StandardMailActionManager::actionStateUpdated, this, &KMMainWidget::slotAkonadiStandardActionUpdated); mAkonadiStandardActionManager->setCollectionSelectionModel(mFolderTreeWidget->folderTreeView()->selectionModel()); mAkonadiStandardActionManager->setItemSelectionModel(mMessagePane->currentItemSelectionModel()); if (mEnableFavoriteFolderView) { mFavoriteCollectionsView = new FavoriteCollectionWidget(KMKernel::self()->mailCommonSettings(), mGUIClient, this); refreshFavoriteFoldersViewProperties(); connect(mFavoriteCollectionsView, SIGNAL(currentChanged(Akonadi::Collection)), this, SLOT(slotFolderChanged(Akonadi::Collection))); connect(mFavoriteCollectionsView, &FavoriteCollectionWidget::newTabRequested, this, &KMMainWidget::slotCreateNewTab); mFavoritesModel = new Akonadi::FavoriteCollectionsModel( mFolderTreeWidget->folderTreeView()->model(), KMKernel::self()->config()->group("FavoriteCollections"), mFavoriteCollectionsView); mFavoriteCollectionsView->setModel(mFavoritesModel); mAkonadiStandardActionManager->setFavoriteCollectionsModel(mFavoritesModel); mAkonadiStandardActionManager->setFavoriteSelectionModel(mFavoriteCollectionsView->selectionModel()); } //Don't use mMailActionManager->createAllActions() to save memory by not //creating actions that doesn't make sense. const auto standardActions = { StandardActionManager::CreateCollection, StandardActionManager::CopyCollections, StandardActionManager::DeleteCollections, StandardActionManager::SynchronizeCollections, StandardActionManager::CollectionProperties, StandardActionManager::CopyItems, StandardActionManager::Paste, StandardActionManager::DeleteItems, StandardActionManager::ManageLocalSubscriptions, StandardActionManager::CopyCollectionToMenu, StandardActionManager::CopyItemToMenu, StandardActionManager::MoveItemToMenu, StandardActionManager::MoveCollectionToMenu, StandardActionManager::CutItems, StandardActionManager::CutCollections, StandardActionManager::CreateResource, StandardActionManager::DeleteResources, StandardActionManager::ResourceProperties, StandardActionManager::SynchronizeResources, StandardActionManager::ToggleWorkOffline, StandardActionManager::SynchronizeCollectionsRecursive, }; for (StandardActionManager::Type standardAction : standardActions) { mAkonadiStandardActionManager->createAction(standardAction); } if (mEnableFavoriteFolderView) { const auto favoriteActions = { StandardActionManager::AddToFavoriteCollections, StandardActionManager::RemoveFromFavoriteCollections, StandardActionManager::RenameFavoriteCollection, StandardActionManager::SynchronizeFavoriteCollections, }; for (StandardActionManager::Type favoriteAction : favoriteActions) { mAkonadiStandardActionManager->createAction(favoriteAction); } } const auto mailActions = { StandardMailActionManager::MarkAllMailAsRead, StandardMailActionManager::MoveToTrash, StandardMailActionManager::MoveAllToTrash, StandardMailActionManager::RemoveDuplicates, StandardMailActionManager::EmptyAllTrash, StandardMailActionManager::MarkMailAsRead, StandardMailActionManager::MarkMailAsUnread, StandardMailActionManager::MarkMailAsImportant, StandardMailActionManager::MarkMailAsActionItem }; for (StandardMailActionManager::Type mailAction : mailActions) { mAkonadiStandardActionManager->createAction(mailAction); } mAkonadiStandardActionManager->interceptAction(Akonadi::StandardActionManager::CollectionProperties); connect(mAkonadiStandardActionManager->action( Akonadi::StandardActionManager::CollectionProperties), &QAction::triggered, mManageShowCollectionProperties, &ManageShowCollectionProperties::slotCollectionProperties); // // Create all kinds of actions // mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::RemoveDuplicates)->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Asterisk)); mAkonadiStandardActionManager->interceptAction(Akonadi::StandardMailActionManager::RemoveDuplicates); connect(mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::RemoveDuplicates), &QAction::triggered, this, &KMMainWidget::slotRemoveDuplicates); mCollectionProperties = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CollectionProperties); connect(kmkernel->folderCollectionMonitor(), &Monitor::collectionRemoved, this, &KMMainWidget::slotCollectionRemoved); connect(kmkernel->folderCollectionMonitor(), &Monitor::itemAdded, this, &KMMainWidget::slotItemAdded); connect(kmkernel->folderCollectionMonitor(), &Monitor::itemRemoved, this, &KMMainWidget::slotItemRemoved); connect(kmkernel->folderCollectionMonitor(), &Monitor::itemMoved, this, &KMMainWidget::slotItemMoved); connect(kmkernel->folderCollectionMonitor(), SIGNAL(collectionChanged(Akonadi::Collection,QSet)), SLOT(slotCollectionChanged(Akonadi::Collection,QSet))); connect(kmkernel->folderCollectionMonitor(), &Monitor::collectionStatisticsChanged, this, &KMMainWidget::slotCollectionStatisticsChanged); } void KMMainWidget::updateMoveAction(const Akonadi::CollectionStatistics &statistic) { const bool hasUnreadMails = (statistic.unreadCount() > 0); updateMoveAction(hasUnreadMails); } void KMMainWidget::updateMoveAction(bool hasUnreadMails) { const bool enable_goto_unread = hasUnreadMails || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllFolders) || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllMarkedFolders); actionCollection()->action(QStringLiteral("go_next_unread_message"))->setEnabled(enable_goto_unread); actionCollection()->action(QStringLiteral("go_prev_unread_message"))->setEnabled(enable_goto_unread); if (mAkonadiStandardActionManager && mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkAllMailAsRead)) { mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkAllMailAsRead)->setEnabled(hasUnreadMails); } } void KMMainWidget::updateAllToTrashAction(int statistics) { if (mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)) { const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural(); mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)->setEnabled(folderWithContent && (statistics > 0) && mCurrentFolderSettings->canDeleteMessages()); } } void KMMainWidget::slotCollectionStatisticsChanged(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistic) { if (id == CommonKernel->outboxCollectionFolder().id()) { const bool enableAction = (statistic.count() > 0); mSendQueued->setEnabled(enableAction); mSendActionMenu->setEnabled(enableAction); } else if (id == mCurrentCollection.id()) { updateMoveAction(statistic); updateAllToTrashAction(statistic.count()); setCurrentCollection(CommonKernel->collectionFromId(mCurrentCollection.id())); } } void KMMainWidget::slotCreateNewTab(bool preferNewTab) { mMessagePane->setPreferEmptyTab(preferNewTab); } void KMMainWidget::slotCollectionChanged(const Akonadi::Collection &collection, const QSet &set) { if ((collection == mCurrentCollection) && (set.contains("MESSAGEFOLDER") || set.contains("expirationcollectionattribute"))) { if (set.contains("MESSAGEFOLDER")) { mMessagePane->resetModelStorage(); } else { setCurrentCollection(collection); } } else if (set.contains("ENTITYDISPLAY") || set.contains("NAME")) { const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(KMKernel::self()->collectionModel(), collection); if (idx.isValid()) { const QString text = idx.data().toString(); const QIcon icon = idx.data(Qt::DecorationRole).value(); mMessagePane->updateTabIconText(collection, text, icon); } } } void KMMainWidget::slotItemAdded(const Akonadi::Item &msg, const Akonadi::Collection &col) { Q_UNUSED(msg); if (col.isValid()) { if (col == CommonKernel->outboxCollectionFolder()) { startUpdateMessageActionsTimer(); } } } void KMMainWidget::slotItemRemoved(const Akonadi::Item &item) { if (item.isValid() && item.parentCollection().isValid() && (item.parentCollection() == CommonKernel->outboxCollectionFolder())) { startUpdateMessageActionsTimer(); } } void KMMainWidget::slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &from, const Akonadi::Collection &to) { if (item.isValid() && ((from.id() == CommonKernel->outboxCollectionFolder().id()) || to.id() == CommonKernel->outboxCollectionFolder().id())) { startUpdateMessageActionsTimer(); } } //------------------------------------------------------------------------- void KMMainWidget::slotFocusQuickSearch() { const QString text = mMsgView ? mMsgView->copyText() : QString(); mMessagePane->focusQuickSearch(text); } //------------------------------------------------------------------------- bool KMMainWidget::slotSearch() { if (!mSearchWin) { mSearchWin = new SearchWindow(this, mCurrentCollection); mSearchWin->setModal(false); mSearchWin->setObjectName(QStringLiteral("Search")); } else { mSearchWin->activateFolder(mCurrentCollection); } mSearchWin->show(); KWindowSystem::activateWindow(mSearchWin->winId()); return true; } //----------------------------------------------------------------------------- void KMMainWidget::slotFilter() { FilterIf->openFilterDialog(true); } void KMMainWidget::slotManageSieveScripts() { if (!kmkernel->askToGoOnline()) { return; } if (mManageSieveDialog) { return; } mManageSieveDialog = new KSieveUi::ManageSieveScriptsDialog(mSievePasswordProvider); connect(mManageSieveDialog.data(), &KSieveUi::ManageSieveScriptsDialog::finished, this, &KMMainWidget::slotCheckVacation); mManageSieveDialog->show(); } //----------------------------------------------------------------------------- void KMMainWidget::slotCheckMail() { kmkernel->checkMail(); } //----------------------------------------------------------------------------- void KMMainWidget::slotCheckMailOnStartup() { kmkernel->checkMailOnStartup(); } void KMMainWidget::slotCompose() { ComposeNewMessageJob *job = new ComposeNewMessageJob; job->setFolderSettings(mCurrentFolderSettings); job->setCurrentCollection(mCurrentCollection); job->start(); } //----------------------------------------------------------------------------- // TODO: do we want the list sorted alphabetically? void KMMainWidget::slotShowNewFromTemplate() { if (mCurrentFolderSettings) { const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoidOrDefault(mCurrentFolderSettings->identity()); mTemplateFolder = CommonKernel->collectionFromId(ident.templates().toLongLong()); } if (!mTemplateFolder.isValid()) { mTemplateFolder = CommonKernel->templatesCollectionFolder(); } if (!mTemplateFolder.isValid()) { return; } mTemplateMenu->menu()->clear(); Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(mTemplateFolder); job->fetchScope().setAncestorRetrieval(ItemFetchScope::Parent); job->fetchScope().fetchFullPayload(); connect(job, &Akonadi::ItemFetchJob::result, this, &KMMainWidget::slotDelayedShowNewFromTemplate); } void KMMainWidget::slotDelayedShowNewFromTemplate(KJob *job) { Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); const Akonadi::Item::List items = fetchJob->items(); const int numberOfItems = items.count(); for (int idx = 0; idx < numberOfItems; ++idx) { KMime::Message::Ptr msg = MessageComposer::Util::message(items.at(idx)); if (msg) { QString subj = msg->subject()->asUnicodeString(); if (subj.isEmpty()) { subj = i18n("No Subject"); } QAction *templateAction = mTemplateMenu->menu()->addAction(KStringHandler::rsqueeze(subj.replace(QLatin1Char('&'), QStringLiteral("&&")))); QVariant var; var.setValue(items.at(idx)); templateAction->setData(var); } } // If there are no templates available, add a menu entry which informs // the user about this. if (mTemplateMenu->menu()->actions().isEmpty()) { QAction *noAction = mTemplateMenu->menu()->addAction( i18n("(no templates)")); noAction->setEnabled(false); } } //----------------------------------------------------------------------------- void KMMainWidget::slotNewFromTemplate(QAction *action) { if (!mTemplateFolder.isValid()) { return; } const Akonadi::Item item = action->data().value(); newFromTemplate(item); } //----------------------------------------------------------------------------- void KMMainWidget::newFromTemplate(const Akonadi::Item &msg) { if (!msg.isValid()) { return; } KMCommand *command = new KMUseTemplateCommand(this, msg); command->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotPostToML() { if (mCurrentFolderSettings && mCurrentFolderSettings->isMailingListEnabled()) { if (KMail::Util::mailingListPost(mCurrentFolderSettings, mCurrentCollection)) { return; } } slotCompose(); } void KMMainWidget::slotExpireFolder() { if (!mCurrentFolderSettings) { return; } bool mustDeleteExpirationAttribute = false; MailCommon::ExpireCollectionAttribute *attr = MailCommon::Util::expirationCollectionAttribute(mCurrentCollection, mustDeleteExpirationAttribute); bool canBeExpired = true; if (!attr->isAutoExpire()) { canBeExpired = false; } else if (attr->unreadExpireUnits() == MailCommon::ExpireCollectionAttribute::ExpireNever && attr->readExpireUnits() == MailCommon::ExpireCollectionAttribute::ExpireNever) { canBeExpired = false; } if (!canBeExpired) { const QString message = i18n("This folder does not have any expiry options set"); KMessageBox::information(this, message); if (mustDeleteExpirationAttribute) { delete attr; } return; } if (KMailSettings::self()->warnBeforeExpire()) { const QString message = i18n("Are you sure you want to expire the folder %1?", mCurrentFolderSettings->name().toHtmlEscaped()); if (KMessageBox::warningContinueCancel(this, message, i18n("Expire Folder"), KGuiItem(i18n("&Expire"))) != KMessageBox::Continue) { if (mustDeleteExpirationAttribute) { delete attr; } return; } } MailCommon::Util::expireOldMessages(mCurrentCollection, true /*immediate*/); if (mustDeleteExpirationAttribute) { delete attr; } } //----------------------------------------------------------------------------- void KMMainWidget::slotEmptyFolder() { if (!mCurrentCollection.isValid()) { return; } const bool isTrash = CommonKernel->folderIsTrash(mCurrentCollection); const QString title = (isTrash) ? i18n("Empty Trash") : i18n("Move to Trash"); const QString text = (isTrash) ? i18n("Are you sure you want to empty the trash folder?") : i18n("Are you sure you want to move all messages from " "folder %1 to the trash?", mCurrentCollection.name().toHtmlEscaped()); if (KMessageBox::warningContinueCancel(this, text, title, KGuiItem(title, QStringLiteral("user-trash"))) != KMessageBox::Continue) { return; } #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif slotSelectAllMessages(); if (isTrash) { /* Don't ask for confirmation again when deleting, the user has already confirmed. */ slotDeleteMsg(false); } else { slotTrashSelectedMessages(); } if (mMsgView) { mMsgView->clearCache(); } if (!isTrash) { const QString str = i18n("Moved all messages to the trash"); showMessageActivities(str); } updateMessageActions(); // Disable empty trash/move all to trash action - we've just deleted/moved // all folder contents. mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)->setEnabled(false); } //----------------------------------------------------------------------------- void KMMainWidget::slotArchiveFolder() { if (mCurrentCollection.isValid()) { QPointer archiveDialog = new KMail::ArchiveFolderDialog(this); archiveDialog->setFolder(mCurrentCollection); archiveDialog->exec(); delete archiveDialog; } } //----------------------------------------------------------------------------- void KMMainWidget::slotRemoveFolder() { if (!mCurrentFolderSettings) { return; } if (!mCurrentFolderSettings->isValid()) { return; } if (mCurrentFolderSettings->isSystemFolder()) { return; } if (mCurrentFolderSettings->isReadOnly()) { return; } RemoveCollectionJob *job = new RemoveCollectionJob(this); connect(job, &RemoveCollectionJob::clearCurrentFolder, this, &KMMainWidget::slotClearCurrentFolder); job->setMainWidget(this); job->setCurrentFolder(mCurrentCollection); job->start(); } void KMMainWidget::slotClearCurrentFolder() { clearCurrentFolder(); } //----------------------------------------------------------------------------- void KMMainWidget::slotExpireAll() { if (KMailSettings::self()->warnBeforeExpire()) { const int ret = KMessageBox::warningContinueCancel(KMainWindow::memberList().first(), i18n("Are you sure you want to expire all old messages?"), i18n("Expire Old Messages?"), KGuiItem(i18n("Expire"))); if (ret != KMessageBox::Continue) { return; } } kmkernel->expireAllFoldersNow(); } //----------------------------------------------------------------------------- void KMMainWidget::slotOverrideHtmlLoadExt() { if (mHtmlLoadExtGlobalSetting == mFolderHtmlLoadExtPreference) { int result = KMessageBox::warningContinueCancel(this, // the warning text is taken from configuredialog.cpp: i18n("Loading external references in html mail will make you more vulnerable to " "\"spam\" and may increase the likelihood that your system will be " "compromised by other present and anticipated security exploits."), i18n("Security Warning"), KGuiItem(i18n("Load External References")), KStandardGuiItem::cancel(), QStringLiteral("OverrideHtmlLoadExtWarning"), nullptr); if (result == KMessageBox::Cancel) { mPreferHtmlLoadExtAction->setChecked(false); return; } } mFolderHtmlLoadExtPreference = !mFolderHtmlLoadExtPreference; if (mMsgView) { mMsgView->setHtmlLoadExtOverride(mFolderHtmlLoadExtPreference); mMsgView->update(true); } } //----------------------------------------------------------------------------- void KMMainWidget::slotMessageQueuedOrDrafted() { if (!CommonKernel->folderIsDraftOrOutbox(mCurrentCollection)) { return; } if (mMsgView) { mMsgView->update(true); } } //----------------------------------------------------------------------------- void KMMainWidget::slotForwardInlineMsg() { if (!mCurrentFolderSettings) { return; } const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } const QString text = mMsgView ? mMsgView->copyText() : QString(); KMForwardCommand *command = new KMForwardCommand( this, selectedMessages, mCurrentFolderSettings->identity(), QString(), text ); command->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotForwardAttachedMessage() { if (!mCurrentFolderSettings) { return; } const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } KMForwardAttachedCommand *command = new KMForwardAttachedCommand( this, selectedMessages, mCurrentFolderSettings->identity() ); command->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotUseTemplate() { newFromTemplate(mMessagePane->currentItem()); } //----------------------------------------------------------------------------- void KMMainWidget::slotResendMsg() { const Akonadi::Item msg = mMessagePane->currentItem(); if (!msg.isValid()) { return; } KMCommand *command = new KMResendMessageCommand(this, msg); command->start(); } //----------------------------------------------------------------------------- // Message moving and permanent deletion // void KMMainWidget::moveMessageSelected(MessageList::Core::MessageItemSetReference ref, const Akonadi::Collection &dest, bool confirmOnDeletion) { Akonadi::Item::List selectMsg = mMessagePane->itemListFromPersistentSet(ref); // If this is a deletion, ask for confirmation if (confirmOnDeletion) { int ret = KMessageBox::warningContinueCancel( this, i18np( "Do you really want to delete the selected message?
" "Once deleted, it cannot be restored.
", "Do you really want to delete the %1 selected messages?
" "Once deleted, they cannot be restored.
", selectMsg.count() ), selectMsg.count() > 1 ? i18n("Delete Messages") : i18n("Delete Message"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral("NoConfirmDelete") ); if (ret == KMessageBox::Cancel) { mMessagePane->deletePersistentSet(ref); return; // user canceled the action } } mMessagePane->markMessageItemsAsAboutToBeRemoved(ref, true); // And stuff them into a KMMoveCommand :) KMMoveCommand *command = new KMMoveCommand(dest, selectMsg, ref); QObject::connect( command, &KMMoveCommand::moveDone, this, &KMMainWidget::slotMoveMessagesCompleted ); command->start(); if (dest.isValid()) { showMessageActivities(i18n("Moving messages...")); } else { showMessageActivities(i18n("Deleting messages...")); } } void KMMainWidget::slotMoveMessagesCompleted(KMMoveCommand *command) { Q_ASSERT(command); mMessagePane->markMessageItemsAsAboutToBeRemoved(command->refSet(), false); mMessagePane->deletePersistentSet(command->refSet()); // Bleah :D const bool moveWasReallyADelete = !command->destFolder().isValid(); QString str; if (command->result() == KMCommand::OK) { if (moveWasReallyADelete) { str = i18n("Messages deleted successfully."); } else { str = i18n("Messages moved successfully."); } } else { if (moveWasReallyADelete) { if (command->result() == KMCommand::Failed) { str = i18n("Deleting messages failed."); } else { str = i18n("Deleting messages canceled."); } } else { if (command->result() == KMCommand::Failed) { str = i18n("Moving messages failed."); } else { str = i18n("Moving messages canceled."); } } } showMessageActivities(str); // The command will autodelete itself and will also kill the set. } void KMMainWidget::slotDeleteMessages() { slotDeleteMsg(true); } Akonadi::Item::List KMMainWidget::currentSelection() const { Akonadi::Item::List selectMsg; MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet(); if (ref != -1) { selectMsg = mMessagePane->itemListFromPersistentSet(ref); } return selectMsg; } void KMMainWidget::slotDeleteMsg(bool confirmDelete) { // Create a persistent message set from the current selection MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet(); if (ref != -1) { moveMessageSelected(ref, Akonadi::Collection(), confirmDelete); } } void KMMainWidget::slotDeleteThread(bool confirmDelete) { // Create a persistent set from the current thread. MessageList::Core::MessageItemSetReference ref = mMessagePane->currentThreadAsPersistentSet(); if (ref != -1) { moveMessageSelected(ref, Akonadi::Collection(), confirmDelete); } } FolderSelectionDialog *KMMainWidget::moveOrCopyToDialog() { if (!mMoveOrCopyToDialog) { FolderSelectionDialog::SelectionFolderOption options = FolderSelectionDialog::HideVirtualFolder; mMoveOrCopyToDialog = new FolderSelectionDialog(this, options); mMoveOrCopyToDialog->setModal(true); } return mMoveOrCopyToDialog; } FolderSelectionDialog *KMMainWidget::selectFromAllFoldersDialog() { if (!mSelectFromAllFoldersDialog) { FolderSelectionDialog::SelectionFolderOptions options = FolderSelectionDialog::None; options |= FolderSelectionDialog::NotAllowToCreateNewFolder; mSelectFromAllFoldersDialog = new FolderSelectionDialog(this, options); mSelectFromAllFoldersDialog->setModal(true); } return mSelectFromAllFoldersDialog; } void KMMainWidget::slotMoveSelectedMessageToFolder() { QPointer dialog(moveOrCopyToDialog()); dialog->setWindowTitle(i18n("Move Messages to Folder")); if (dialog->exec() && dialog) { const Akonadi::Collection dest = dialog->selectedCollection(); if (dest.isValid()) { moveSelectedMessagesToFolder(dest); } } } void KMMainWidget::moveSelectedMessagesToFolder(const Akonadi::Collection &dest) { MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet(); if (ref != -1) { //Need to verify if dest == src ??? akonadi do it for us. moveMessageSelected(ref, dest, false); } } void KMMainWidget::copyMessageSelected(const Akonadi::Item::List &selectMsg, const Akonadi::Collection &dest) { if (selectMsg.isEmpty()) { return; } // And stuff them into a KMCopyCommand :) KMCommand *command = new KMCopyCommand(dest, selectMsg); QObject::connect( command, &KMCommand::completed, this, &KMMainWidget::slotCopyMessagesCompleted ); command->start(); showMessageActivities(i18n("Copying messages...")); } void KMMainWidget::slotCopyMessagesCompleted(KMCommand *command) { Q_ASSERT(command); QString str; if (command->result() == KMCommand::OK) { str = i18n("Messages copied successfully."); } else { if (command->result() == KMCommand::Failed) { str = i18n("Copying messages failed."); } else { str = i18n("Copying messages canceled."); } } showMessageActivities(str); // The command will autodelete itself and will also kill the set. } void KMMainWidget::slotCopySelectedMessagesToFolder() { QPointer dialog(moveOrCopyToDialog()); dialog->setWindowTitle(i18n("Copy Messages to Folder")); if (dialog->exec() && dialog) { const Akonadi::Collection dest = dialog->selectedCollection(); if (dest.isValid()) { copySelectedMessagesToFolder(dest); } } } void KMMainWidget::copySelectedMessagesToFolder(const Akonadi::Collection &dest) { const Akonadi::Item::List lstMsg = mMessagePane->selectionAsMessageItemList(); if (!lstMsg.isEmpty()) { copyMessageSelected(lstMsg, dest); } } //----------------------------------------------------------------------------- // Message trashing // void KMMainWidget::trashMessageSelected(MessageList::Core::MessageItemSetReference ref) { if (!mCurrentCollection.isValid()) { return; } const Akonadi::Item::List select = mMessagePane->itemListFromPersistentSet(ref); mMessagePane->markMessageItemsAsAboutToBeRemoved(ref, true); // FIXME: Why we don't use KMMoveCommand( trashFolder(), selectedMessages ); ? // And stuff them into a KMTrashMsgCommand :) KMTrashMsgCommand *command = new KMTrashMsgCommand(mCurrentCollection, select, ref); QObject::connect( command, SIGNAL(moveDone(KMMoveCommand *)), this, SLOT(slotTrashMessagesCompleted(KMMoveCommand *)) ); command->start(); bool moveToTrash = command->destFolder().isValid(); showMessageActivities(moveToTrash ? i18n("Moving messages to trash...") : i18n("Deleting messages...")); } void KMMainWidget::slotTrashMessagesCompleted(KMMoveCommand *command) { Q_ASSERT(command); mMessagePane->markMessageItemsAsAboutToBeRemoved(command->refSet(), false); mMessagePane->deletePersistentSet(command->refSet()); bool moveToTrash = command->destFolder().isValid(); if (command->result() == KMCommand::OK) { showMessageActivities(moveToTrash ? i18n("Messages moved to trash successfully.") : i18n("Messages deleted successfully.")); } else { if (command->result() == KMCommand::Failed) { showMessageActivities(moveToTrash ? i18n("Moving messages to trash failed.") : i18n("Deleting messages failed.")); } else { showMessageActivities(moveToTrash ? i18n("Moving messages to trash canceled.") : i18n("Deleting messages canceled.")); } } // The command will autodelete itself and will also kill the set. } void KMMainWidget::slotTrashSelectedMessages() { MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet(); if (ref != -1) { trashMessageSelected(ref); } } void KMMainWidget::slotTrashThread() { MessageList::Core::MessageItemSetReference ref = mMessagePane->currentThreadAsPersistentSet(); if (ref != -1) { trashMessageSelected(ref); } } //----------------------------------------------------------------------------- // Message tag setting for messages // // FIXME: The "selection" version of these functions is in MessageActions. // We should probably move everything there.... void KMMainWidget::toggleMessageSetTag(const Akonadi::Item::List &select, const Akonadi::Tag &tag) { if (select.isEmpty()) { return; } KMCommand *command = new KMSetTagCommand(Akonadi::Tag::List() << tag, select, KMSetTagCommand::Toggle); command->start(); } void KMMainWidget::slotSelectMoreMessageTagList() { const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } QPointer dlg = new TagSelectDialog(this, selectedMessages.count(), selectedMessages.first()); dlg->setActionCollection(QList { actionCollection() }); if (dlg->exec()) { const Akonadi::Tag::List lst = dlg->selectedTag(); KMCommand *command = new KMSetTagCommand(lst, selectedMessages, KMSetTagCommand::CleanExistingAndAddNew); command->start(); } delete dlg; } void KMMainWidget::slotUpdateMessageTagList(const Akonadi::Tag &tag) { // Create a persistent set from the current thread. const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } toggleMessageSetTag(selectedMessages, tag); } void KMMainWidget::refreshMessageListSelection() { mAkonadiStandardActionManager->setItemSelectionModel(mMessagePane->currentItemSelectionModel()); slotMessageSelected(mMessagePane->currentItem()); Q_EMIT captionChangeRequest(MailCommon::Util::fullCollectionPath(mMessagePane->currentFolder())); } //----------------------------------------------------------------------------- // Status setting for threads // // FIXME: The "selection" version of these functions is in MessageActions. // We should probably move everything there.... void KMMainWidget::setMessageSetStatus(const Akonadi::Item::List &select, const Akonadi::MessageStatus &status, bool toggle) { KMCommand *command = new KMSetStatusCommand(status, select, toggle); command->start(); } void KMMainWidget::setCurrentThreadStatus(const Akonadi::MessageStatus &status, bool toggle) { const Akonadi::Item::List select = mMessagePane->currentThreadAsMessageList(); if (select.isEmpty()) { return; } setMessageSetStatus(select, status, toggle); } void KMMainWidget::slotSetThreadStatusUnread() { setCurrentThreadStatus(MessageStatus::statusRead(), true); } void KMMainWidget::slotSetThreadStatusImportant() { setCurrentThreadStatus(MessageStatus::statusImportant(), true); } void KMMainWidget::slotSetThreadStatusRead() { setCurrentThreadStatus(MessageStatus::statusRead(), false); } void KMMainWidget::slotSetThreadStatusToAct() { setCurrentThreadStatus(MessageStatus::statusToAct(), true); } void KMMainWidget::slotSetThreadStatusWatched() { setCurrentThreadStatus(MessageStatus::statusWatched(), true); if (mWatchThreadAction->isChecked()) { mIgnoreThreadAction->setChecked(false); } } void KMMainWidget::slotSetThreadStatusIgnored() { setCurrentThreadStatus(MessageStatus::statusIgnored(), true); if (mIgnoreThreadAction->isChecked()) { mWatchThreadAction->setChecked(false); } } //----------------------------------------------------------------------------- void KMMainWidget::slotRedirectMessage() { const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } KMCommand *command = new KMRedirectCommand(this, selectedMessages); command->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotCustomReplyToMsg(const QString &tmpl) { const Akonadi::Item msg = mMessagePane->currentItem(); if (!msg.isValid()) { return; } const QString text = mMsgView ? mMsgView->copyText() : QString(); qCDebug(KMAIL_LOG) << "Reply with template:" << tmpl; KMCommand *command = new KMReplyCommand(this, msg, MessageComposer::ReplySmart, text, false, tmpl); command->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotCustomReplyAllToMsg(const QString &tmpl) { const Akonadi::Item msg = mMessagePane->currentItem(); if (!msg.isValid()) { return; } const QString text = mMsgView ? mMsgView->copyText() : QString(); qCDebug(KMAIL_LOG) << "Reply to All with template:" << tmpl; KMCommand *command = new KMReplyCommand(this, msg, MessageComposer::ReplyAll, text, false, tmpl ); command->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotCustomForwardMsg(const QString &tmpl) { if (!mCurrentFolderSettings) { return; } const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } const QString text = mMsgView ? mMsgView->copyText() : QString(); qCDebug(KMAIL_LOG) << "Forward with template:" << tmpl; KMForwardCommand *command = new KMForwardCommand( this, selectedMessages, mCurrentFolderSettings->identity(), tmpl, text ); command->start(); } void KMMainWidget::openFilterDialog(const QByteArray &field, const QString &value) { FilterIf->openFilterDialog(false); FilterIf->createFilter(field, value); } //----------------------------------------------------------------------------- void KMMainWidget::slotSubjectFilter() { const KMime::Message::Ptr msg = mMessagePane->currentMessage(); if (!msg) { return; } openFilterDialog("Subject", msg->subject()->asUnicodeString()); } //----------------------------------------------------------------------------- void KMMainWidget::slotFromFilter() { KMime::Message::Ptr msg = mMessagePane->currentMessage(); if (!msg) { return; } AddrSpecList al = MessageHelper::extractAddrSpecs(msg, "From"); if (al.empty()) { openFilterDialog("From", msg->from()->asUnicodeString()); } else { openFilterDialog("From", al.front().asString()); } } //----------------------------------------------------------------------------- void KMMainWidget::slotToFilter() { KMime::Message::Ptr msg = mMessagePane->currentMessage(); if (!msg) { return; } openFilterDialog("To", msg->to()->asUnicodeString()); } void KMMainWidget::slotCcFilter() { KMime::Message::Ptr msg = mMessagePane->currentMessage(); if (!msg) { return; } openFilterDialog("Cc", msg->cc()->asUnicodeString()); } void KMMainWidget::slotBandwidth(bool b) { PimCommon::NetworkUtil::self()->setLowBandwidth(b); } //----------------------------------------------------------------------------- void KMMainWidget::slotUndo() { kmkernel->undoStack()->undo(); updateMessageActions(); updateFolderMenu(); } //----------------------------------------------------------------------------- void KMMainWidget::slotJumpToFolder() { QPointer dialog(selectFromAllFoldersDialog()); dialog->setWindowTitle(i18n("Jump to Folder")); if (dialog->exec() && dialog) { Akonadi::Collection collection = dialog->selectedCollection(); if (collection.isValid()) { slotSelectCollectionFolder(collection); } } } void KMMainWidget::slotSelectCollectionFolder(const Akonadi::Collection &col) { if (mFolderTreeWidget) { mFolderTreeWidget->selectCollectionFolder(col); slotFolderChanged(col); } } void KMMainWidget::slotApplyFilters() { const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } applyFilters(selectedMessages); } Akonadi::Collection::List KMMainWidget::applyFilterOnCollection(bool recursive) { Akonadi::Collection::List cols; if (recursive) { cols = KMKernel::self()->subfolders(mCurrentCollection); } else { cols << mCurrentCollection; } return cols; } void KMMainWidget::slotApplyFiltersOnFolder(bool recursive) { if (mCurrentCollection.isValid()) { const Akonadi::Collection::List cols = applyFilterOnCollection(recursive); applyFilters(cols); } } void KMMainWidget::slotApplyFilterOnFolder(bool recursive) { if (mCurrentCollection.isValid()) { const Akonadi::Collection::List cols = applyFilterOnCollection(recursive); QAction *action = qobject_cast(sender()); applyFilter(cols, action->property("filter_id").toString()); } } void KMMainWidget::applyFilters(const Akonadi::Item::List &selectedMessages) { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif MailCommon::FilterManager::instance()->filter(selectedMessages); } void KMMainWidget::applyFilters(const Akonadi::Collection::List &selectedCols) { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif MailCommon::FilterManager::instance()->filter(selectedCols); } void KMMainWidget::applyFilter(const Akonadi::Collection::List &selectedCols, const QString &filter) { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif MailCommon::FilterManager::instance()->filter(selectedCols, { filter }); } //----------------------------------------------------------------------------- void KMMainWidget::slotCheckVacation() { updateVacationScriptStatus(false); if (!kmkernel->askToGoOnline()) { return; } mVacationManager->checkVacation(); } void KMMainWidget::slotEditCurrentVacation() { slotEditVacation(QString()); } void KMMainWidget::slotEditVacation(const QString &serverName) { if (!kmkernel->askToGoOnline()) { return; } mVacationManager->slotEditVacation(serverName); } //----------------------------------------------------------------------------- void KMMainWidget::slotDebugSieve() { #if !defined(NDEBUG) QPointer mSieveDebugDialog = new KSieveUi::SieveDebugDialog(mSievePasswordProvider, this); mSieveDebugDialog->exec(); delete mSieveDebugDialog; #endif } void KMMainWidget::slotConfigChanged() { readConfig(); mMsgActions->setupForwardActions(actionCollection()); mMsgActions->setupForwardingActionsList(mGUIClient); } //----------------------------------------------------------------------------- void KMMainWidget::slotSaveMsg() { const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand(this, selectedMessages); saveCommand->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotOpenMsg() { KMOpenMsgCommand *openCommand = new KMOpenMsgCommand(this, QUrl(), overrideEncoding(), this); openCommand->start(); } //----------------------------------------------------------------------------- void KMMainWidget::slotSaveAttachments() { const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); if (selectedMessages.isEmpty()) { return; } // Avoid re-downloading in the common case that only one message is selected, and the message // is also displayed in the viewer. For this, create a dummy item without a parent collection / item id, // so that KMCommand doesn't download it. KMSaveAttachmentsCommand *saveCommand = nullptr; if (mMsgView && selectedMessages.size() == 1 && mMsgView->message().hasPayload() && selectedMessages.first().id() == mMsgView->message().id()) { Akonadi::Item dummyItem; dummyItem.setPayload(mMsgView->message().payload()); saveCommand = new KMSaveAttachmentsCommand(this, dummyItem, mMsgView->viewer()); } else { saveCommand = new KMSaveAttachmentsCommand(this, selectedMessages); } saveCommand->start(); } void KMMainWidget::slotOnlineStatus() { // KMKernel will emit a signal when we toggle the network state that is caught by // KMMainWidget::slotUpdateOnlineStatus to update our GUI if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Online) { // if online; then toggle and set it offline. kmkernel->stopNetworkJobs(); } else { kmkernel->resumeNetworkJobs(); slotCheckVacation(); } } void KMMainWidget::slotUpdateOnlineStatus(KMailSettings::EnumNetworkState::type) { if (!mAkonadiStandardActionManager) { return; } QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::ToggleWorkOffline); if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Online) { action->setText(i18n("Work Offline")); action->setIcon(QIcon::fromTheme(QStringLiteral("user-offline"))); } else { action->setText(i18n("Work Online")); action->setIcon(QIcon::fromTheme(QStringLiteral("user-online"))); } } //----------------------------------------------------------------------------- void KMMainWidget::slotSendQueued() { if (kmkernel->msgSender()) { kmkernel->msgSender()->sendQueued(); } } //----------------------------------------------------------------------------- void KMMainWidget::slotSendQueuedVia(MailTransport::Transport *transport) { if (transport) { if (kmkernel->msgSender()) { kmkernel->msgSender()->sendQueued(transport->id()); } } } //----------------------------------------------------------------------------- void KMMainWidget::slotShowBusySplash() { if (mReaderWindowActive) { mMsgView->displayBusyPage(); } } void KMMainWidget::showOfflinePage() { if (!mReaderWindowActive) { return; } mMsgView->displayOfflinePage(); } void KMMainWidget::showResourceOfflinePage() { if (!mReaderWindowActive) { return; } mMsgView->displayResourceOfflinePage(); } void KMMainWidget::slotFocusOnNextMessage() { mMessagePane->focusNextMessageItem(MessageList::Core::MessageTypeAny, true, false); } void KMMainWidget::slotFocusOnPrevMessage() { mMessagePane->focusPreviousMessageItem(MessageList::Core::MessageTypeAny, true, false); } void KMMainWidget::slotSelectFirstMessage() { mMessagePane->selectFirstMessageItem(MessageList::Core::MessageTypeAny, true); } void KMMainWidget::slotSelectLastMessage() { mMessagePane->selectLastMessageItem(MessageList::Core::MessageTypeAny, true); } void KMMainWidget::slotSelectFocusedMessage() { mMessagePane->selectFocusedMessageItem(true); } void KMMainWidget::slotSelectNextMessage() { mMessagePane->selectNextMessageItem(MessageList::Core::MessageTypeAny, MessageList::Core::ClearExistingSelection, true, false); } void KMMainWidget::slotExtendSelectionToNextMessage() { mMessagePane->selectNextMessageItem( MessageList::Core::MessageTypeAny, MessageList::Core::GrowOrShrinkExistingSelection, true, // center item false // don't loop in folder ); } void KMMainWidget::slotSelectNextUnreadMessage() { // The looping logic is: "Don't loop" just never loops, "Loop in current folder" // loops just in current folder, "Loop in all folders" loops in the current folder // first and then after confirmation jumps to the next folder. // A bad point here is that if you answer "No, and don't ask me again" to the confirmation // dialog then you have "Loop in current folder" and "Loop in all folders" that do // the same thing and no way to get the old behaviour. However, after a consultation on #kontact, // for bug-to-bug backward compatibility, the masters decided to keep it b0rken :D // If nobody complains, it stays like it is: if you complain enough maybe the masters will // decide to reconsider :) if (!mMessagePane->selectNextMessageItem( MessageList::Core::MessageTypeUnreadOnly, MessageList::Core::ClearExistingSelection, true, // center item KMailSettings::self()->loopOnGotoUnread() != KMailSettings::EnumLoopOnGotoUnread::DontLoop )) { // no next unread message was found in the current folder if ((KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllFolders) || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllMarkedFolders)) { mGoToFirstUnreadMessageInSelectedFolder = true; mFolderTreeWidget->folderTreeView()->selectNextUnreadFolder(true); mGoToFirstUnreadMessageInSelectedFolder = false; } } } void KMMainWidget::slotSelectPreviousMessage() { mMessagePane->selectPreviousMessageItem(MessageList::Core::MessageTypeAny, MessageList::Core::ClearExistingSelection, true, false); } void KMMainWidget::slotExtendSelectionToPreviousMessage() { mMessagePane->selectPreviousMessageItem( MessageList::Core::MessageTypeAny, MessageList::Core::GrowOrShrinkExistingSelection, true, // center item false // don't loop in folder ); } void KMMainWidget::slotSelectPreviousUnreadMessage() { if (!mMessagePane->selectPreviousMessageItem( MessageList::Core::MessageTypeUnreadOnly, MessageList::Core::ClearExistingSelection, true, // center item KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInCurrentFolder )) { // no next unread message was found in the current folder if ((KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllFolders) || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllMarkedFolders)) { mGoToFirstUnreadMessageInSelectedFolder = true; mFolderTreeWidget->folderTreeView()->selectPrevUnreadFolder(); mGoToFirstUnreadMessageInSelectedFolder = false; } } } void KMMainWidget::slotDisplayCurrentMessage() { if (mMessagePane->currentItem().isValid() && !mMessagePane->searchEditHasFocus()) { slotMessageActivated(mMessagePane->currentItem()); } } // Called by double-clicked or 'Enter' in the messagelist -> pop up reader window void KMMainWidget::slotMessageActivated(const Akonadi::Item &msg) { if (!mCurrentCollection.isValid() || !msg.isValid()) { return; } if (CommonKernel->folderIsDraftOrOutbox(mCurrentCollection)) { mMsgActions->setCurrentMessage(msg); mMsgActions->editCurrentMessage(); return; } if (CommonKernel->folderIsTemplates(mCurrentCollection)) { slotUseTemplate(); return; } KMReaderMainWin *win = nullptr; if (!mMsgView) { win = new KMReaderMainWin(mFolderDisplayFormatPreference, mFolderHtmlLoadExtPreference); } // Try to fetch the mail, even in offline mode, it might be cached KMFetchMessageCommand *cmd = new KMFetchMessageCommand(this, msg, win ? win->viewer() : mMsgView->viewer(), win); connect(cmd, &KMCommand::completed, this, &KMMainWidget::slotItemsFetchedForActivation); cmd->start(); } void KMMainWidget::slotItemsFetchedForActivation(KMCommand *command) { KMCommand::Result result = command->result(); if (result != KMCommand::OK) { qCDebug(KMAIL_LOG) << "Result:" << result; return; } KMFetchMessageCommand *fetchCmd = qobject_cast(command); const Item msg = fetchCmd->item(); KMReaderMainWin *win = fetchCmd->readerMainWin(); if (!win) { win = new KMReaderMainWin(mFolderDisplayFormatPreference, mFolderHtmlLoadExtPreference); } const bool useFixedFont = mMsgView ? mMsgView->isFixedFont() : MessageViewer::MessageViewerSettings::self()->useFixedFont(); win->setUseFixedFont(useFixedFont); win->showMessage(overrideEncoding(), msg, CommonKernel->collectionFromId(msg.parentCollection().id())); win->show(); } void KMMainWidget::slotMessageStatusChangeRequest(const Akonadi::Item &item, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear) { if (!item.isValid()) { return; } if (clear.toQInt32() != Akonadi::MessageStatus().toQInt32()) { KMCommand *command = new KMSetStatusCommand(clear, Akonadi::Item::List() << item, true); command->start(); } if (set.toQInt32() != Akonadi::MessageStatus().toQInt32()) { KMCommand *command = new KMSetStatusCommand(set, Akonadi::Item::List() << item, false); command->start(); } } //----------------------------------------------------------------------------- void KMMainWidget::slotSelectAllMessages() { mMessagePane->selectAll(); updateMessageActions(); } void KMMainWidget::slotMessagePopup(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &aPoint) { QUrl aUrl = result.linkUrl(); QUrl imageUrl = result.imageUrl(); updateMessageMenu(); const QString email = KEmailAddress::firstEmailAddress(aUrl.path()).toLower(); if (aUrl.scheme() == QLatin1String("mailto") && !email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(this); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email, Akonadi::ContactSearchJob::ExactMatch); job->setProperty("msg", QVariant::fromValue(msg)); job->setProperty("point", aPoint); job->setProperty("imageUrl", imageUrl); job->setProperty("url", aUrl); job->setProperty("webhitresult", QVariant::fromValue(result)); connect(job, &Akonadi::ContactSearchJob::result, this, &KMMainWidget::slotContactSearchJobForMessagePopupDone); } else { showMessagePopup(msg, aUrl, imageUrl, aPoint, false, false, result); } } void KMMainWidget::slotContactSearchJobForMessagePopupDone(KJob *job) { const Akonadi::ContactSearchJob *searchJob = qobject_cast(job); const bool contactAlreadyExists = !searchJob->contacts().isEmpty(); const Akonadi::Item::List listContact = searchJob->items(); const bool uniqueContactFound = (listContact.count() == 1); if (uniqueContactFound) { mMsgView->setContactItem(listContact.first(), searchJob->contacts().at(0)); } else { mMsgView->clearContactItem(); } const Akonadi::Item msg = job->property("msg").value(); const QPoint aPoint = job->property("point").toPoint(); const QUrl imageUrl = job->property("imageUrl").toUrl(); const QUrl url = job->property("url").toUrl(); const WebEngineViewer::WebHitTestResult result = job->property("webhitresult").value(); showMessagePopup(msg, url, imageUrl, aPoint, contactAlreadyExists, uniqueContactFound, result); } void KMMainWidget::showMessagePopup(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &aPoint, bool contactAlreadyExists, bool uniqueContactFound, const WebEngineViewer::WebHitTestResult &result) { QMenu menu(this); bool urlMenuAdded = false; if (!url.isEmpty()) { if (url.scheme() == QLatin1String("mailto")) { // popup on a mailto URL menu.addAction(mMsgView->mailToComposeAction()); menu.addAction(mMsgView->mailToReplyAction()); menu.addAction(mMsgView->mailToForwardAction()); menu.addSeparator(); if (contactAlreadyExists) { if (uniqueContactFound) { menu.addAction(mMsgView->editContactAction()); } else { menu.addAction(mMsgView->openAddrBookAction()); } } else { menu.addAction(mMsgView->addAddrBookAction()); menu.addAction(mMsgView->addToExistingContactAction()); } menu.addSeparator(); menu.addMenu(mMsgView->viewHtmlOption()); menu.addSeparator(); menu.addAction(mMsgView->copyURLAction()); urlMenuAdded = true; } else if (url.scheme() != QLatin1String("attachment")) { // popup on a not-mailto URL menu.addAction(mMsgView->urlOpenAction()); menu.addAction(mMsgView->addBookmarksAction()); menu.addAction(mMsgView->urlSaveAsAction()); menu.addAction(mMsgView->copyURLAction()); menu.addSeparator(); menu.addAction(mMsgView->shareServiceUrlMenu()); menu.addActions(mMsgView->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedUrl)); if (!imageUrl.isEmpty()) { menu.addSeparator(); menu.addAction(mMsgView->copyImageLocation()); menu.addAction(mMsgView->downloadImageToDiskAction()); menu.addAction(mMsgView->shareImage()); } urlMenuAdded = true; } qCDebug(KMAIL_LOG) << "URL is:" << url; } const QString selectedText = mMsgView ? mMsgView->copyText() : QString(); if (mMsgView && !selectedText.isEmpty()) { if (urlMenuAdded) { menu.addSeparator(); } menu.addAction(mMsgActions->replyMenu()); menu.addSeparator(); menu.addAction(mMsgView->copyAction()); menu.addAction(mMsgView->selectAllAction()); menu.addSeparator(); mMsgActions->addWebShortcutsMenu(&menu, selectedText); menu.addSeparator(); menu.addActions(mMsgView->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedSelection)); if (KPIMTextEdit::TextToSpeech::self()->isReady()) { menu.addSeparator(); menu.addAction(mMsgView->speakTextAction()); } } else if (!urlMenuAdded) { // popup somewhere else (i.e., not a URL) on the message if (!mMessagePane->currentMessage()) { // no messages return; } Akonadi::Collection parentCol = msg.parentCollection(); if (parentCol.isValid() && CommonKernel->folderIsTemplates(parentCol)) { menu.addAction(mNewMessageFromTemplateAction); } else { menu.addAction(mMsgActions->replyMenu()); menu.addAction(mMsgActions->forwardMenu()); } if (parentCol.isValid() && CommonKernel->folderIsSentMailFolder(parentCol)) { menu.addAction(sendAgainAction()); } else { menu.addAction(editAction()); } menu.addAction(mailingListActionMenu()); menu.addSeparator(); menu.addAction(mCopyActionMenu); menu.addAction(mMoveActionMenu); menu.addSeparator(); menu.addAction(mMsgActions->messageStatusMenu()); menu.addSeparator(); if (mMsgView) { if (!imageUrl.isEmpty()) { menu.addSeparator(); menu.addAction(mMsgView->copyImageLocation()); menu.addAction(mMsgView->downloadImageToDiskAction()); menu.addAction(mMsgView->shareImage()); menu.addSeparator(); } } menu.addSeparator(); menu.addAction(mMsgActions->printPreviewAction()); menu.addAction(mMsgActions->printAction()); menu.addSeparator(); menu.addAction(mSaveAsAction); menu.addAction(mSaveAttachmentsAction); menu.addSeparator(); if (parentCol.isValid() && CommonKernel->folderIsTrash(parentCol)) { menu.addAction(mDeleteAction); } else { menu.addAction(akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash)); } menu.addSeparator(); if (mMsgView) { menu.addActions(mMsgView->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedMessage)); menu.addSeparator(); } menu.addAction(mMsgActions->annotateAction()); menu.addSeparator(); menu.addAction(mMsgActions->addFollowupReminderAction()); - if (kmkernel->allowToDebugBalooSupport()) { + if (kmkernel->allowToDebugAkonadiSeachSupport()) { menu.addSeparator(); - menu.addAction(mMsgActions->debugBalooAction()); + menu.addAction(mMsgActions->debugAkonadiSearchAction()); } } if (!selectedText.isEmpty()) { if (mMsgView) { const QList interceptorUrlActions = mMsgView->interceptorUrlActions(result); if (!interceptorUrlActions.isEmpty()) { menu.addSeparator(); menu.addActions(interceptorUrlActions); } } } KAcceleratorManager::manage(&menu); menu.exec(aPoint, nullptr); } void KMMainWidget::setupActions() { KMailPluginInterface::self()->setParentWidget(this); KMailPluginInterface::self()->createPluginInterface(); mMsgActions = new KMail::MessageActions(actionCollection(), this); mMsgActions->setMessageView(mMsgView); //----- File Menu mSaveAsAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save &As..."), this); actionCollection()->addAction(QStringLiteral("file_save_as"), mSaveAsAction); connect(mSaveAsAction, &QAction::triggered, this, &KMMainWidget::slotSaveMsg); actionCollection()->setDefaultShortcut(mSaveAsAction, KStandardShortcut::save().first()); mOpenAction = KStandardAction::open(this, &KMMainWidget::slotOpenMsg, actionCollection()); mOpenRecentAction = KStandardAction::openRecent(this, &KMMainWidget::slotOpenRecentMsg, actionCollection()); KConfigGroup grp = mConfig->group(QStringLiteral("Recent Files")); mOpenRecentAction->loadEntries(grp); { QAction *action = new QAction(i18n("&Expire All Folders"), this); actionCollection()->addAction(QStringLiteral("expire_all_folders"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotExpireAll); } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-receive")), i18n("Check &Mail"), this); actionCollection()->addAction(QStringLiteral("check_mail"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotCheckMail); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L)); } mAccountActionMenu = new KActionMenuAccount(this); mAccountActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-receive"))); mAccountActionMenu->setText(i18n("Check Mail In")); mAccountActionMenu->setIconText(i18n("Check Mail")); mAccountActionMenu->setToolTip(i18n("Check Mail")); actionCollection()->addAction(QStringLiteral("check_mail_in"), mAccountActionMenu); connect(mAccountActionMenu, &KActionMenu::triggered, this, &KMMainWidget::slotCheckMail); mSendQueued = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Queued Messages"), this); actionCollection()->addAction(QStringLiteral("send_queued"), mSendQueued); connect(mSendQueued, &QAction::triggered, this, &KMMainWidget::slotSendQueued); { QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::ToggleWorkOffline); mAkonadiStandardActionManager->interceptAction(Akonadi::StandardActionManager::ToggleWorkOffline); action->setCheckable(false); connect(action, &QAction::triggered, this, &KMMainWidget::slotOnlineStatus); action->setText(i18n("Online status (unknown)")); } mSendActionMenu = new KActionMenuTransport(this); mSendActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); mSendActionMenu->setText(i18n("Send Queued Messages Via")); actionCollection()->addAction(QStringLiteral("send_queued_via"), mSendActionMenu); connect(mSendActionMenu, &KActionMenuTransport::transportSelected, this, &KMMainWidget::slotSendQueuedVia); //----- Tools menu if (parent()->inherits("KMMainWin")) { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this); actionCollection()->addAction(QStringLiteral("addressbook"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotRunAddressBook); if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) { action->setEnabled(false); } } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("pgp-keys")), i18n("Certificate Manager"), this); actionCollection()->addAction(QStringLiteral("tools_start_certman"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotStartCertManager); // disable action if no certman binary is around if (QStandardPaths::findExecutable(QStringLiteral("kleopatra")).isEmpty()) { action->setEnabled(false); } } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("pgp-keys")), i18n("GnuPG Log Viewer"), this); actionCollection()->addAction(QStringLiteral("tools_start_kwatchgnupg"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotStartWatchGnuPG); #ifdef Q_OS_WIN32 // not ported yet, underlying infrastructure missing on Windows const bool usableKWatchGnupg = false; #else // disable action if no kwatchgnupg binary is around bool usableKWatchGnupg = !QStandardPaths::findExecutable(QStringLiteral("kwatchgnupg")).isEmpty(); #endif action->setEnabled(usableKWatchGnupg); } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("document-import")), i18n("&Import Messages..."), this); actionCollection()->addAction(QStringLiteral("import"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotImport); if (QStandardPaths::findExecutable(QStringLiteral("akonadiimportwizard")).isEmpty()) { action->setEnabled(false); } } #if !defined(NDEBUG) { QAction *action = new QAction(i18n("&Debug Sieve..."), this); actionCollection()->addAction(QStringLiteral("tools_debug_sieve"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotDebugSieve); } #endif { QAction *action = new QAction(i18n("Filter &Log Viewer..."), this); actionCollection()->addAction(QStringLiteral("filter_log_viewer"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotFilterLogViewer); } { QAction *action = new QAction(i18n("&Import from another Email Client..."), this); actionCollection()->addAction(QStringLiteral("importWizard"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotImportWizard); } if (KSieveUi::Util::allowOutOfOfficeSettings()) { QAction *action = new QAction(i18n("Edit \"Out of Office\" Replies..."), this); actionCollection()->addAction(QStringLiteral("tools_edit_vacation"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotEditCurrentVacation); } { QAction *action = new QAction(i18n("&Configure Automatic Archiving..."), this); actionCollection()->addAction(QStringLiteral("tools_automatic_archiving"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotConfigureAutomaticArchiving); } { QAction *action = new QAction(i18n("Delayed Messages..."), this); actionCollection()->addAction(QStringLiteral("message_delayed"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotConfigureSendLater); } { QAction *action = new QAction(i18n("Followup Reminder Messages..."), this); actionCollection()->addAction(QStringLiteral("followup_reminder_messages"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotConfigureFollowupReminder); } // Disable the standard action delete key sortcut. QAction *const standardDelAction = akonadiStandardAction(Akonadi::StandardActionManager::DeleteItems); standardDelAction->setShortcut(QKeySequence()); //----- Edit Menu mDeleteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action Hard delete, bypassing trash", "&Delete"), this); actionCollection()->addAction(QStringLiteral("delete"), mDeleteAction); connect(mDeleteAction, &QAction::triggered, this, &KMMainWidget::slotDeleteMessages); actionCollection()->setDefaultShortcut(mDeleteAction, QKeySequence(Qt::SHIFT + Qt::Key_Delete)); mTrashThreadAction = new QAction(i18n("M&ove Thread to Trash"), this); actionCollection()->addAction(QStringLiteral("move_thread_to_trash"), mTrashThreadAction); actionCollection()->setDefaultShortcut(mTrashThreadAction, QKeySequence(Qt::CTRL + Qt::Key_Delete)); mTrashThreadAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); KMail::Util::addQActionHelpText(mTrashThreadAction, i18n("Move thread to trashcan")); connect(mTrashThreadAction, &QAction::triggered, this, &KMMainWidget::slotTrashThread); mDeleteThreadAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete T&hread"), this); actionCollection()->addAction(QStringLiteral("delete_thread"), mDeleteThreadAction); //Don't use new connect api. connect(mDeleteThreadAction, SIGNAL(triggered(bool)), this, SLOT(slotDeleteThread())); actionCollection()->setDefaultShortcut(mDeleteThreadAction, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Delete)); mSearchMessages = new QAction(QIcon::fromTheme(QStringLiteral("edit-find-mail")), i18n("&Find Messages..."), this); actionCollection()->addAction(QStringLiteral("search_messages"), mSearchMessages); connect(mSearchMessages, &QAction::triggered, this, &KMMainWidget::slotRequestFullSearchFromQuickSearch); actionCollection()->setDefaultShortcut(mSearchMessages, QKeySequence(Qt::Key_S)); mSelectAllMessages = new QAction(i18n("Select &All Messages"), this); actionCollection()->addAction(QStringLiteral("mark_all_messages"), mSelectAllMessages); connect(mSelectAllMessages, &QAction::triggered, this, &KMMainWidget::slotSelectAllMessages); actionCollection()->setDefaultShortcut(mSelectAllMessages, QKeySequence(Qt::CTRL + Qt::Key_A)); //----- Folder Menu mFolderMailingListPropertiesAction = new QAction(i18n("&Mailing List Management..."), this); actionCollection()->addAction(QStringLiteral("folder_mailinglist_properties"), mFolderMailingListPropertiesAction); connect(mFolderMailingListPropertiesAction, &QAction::triggered, mManageShowCollectionProperties, &ManageShowCollectionProperties::slotFolderMailingListProperties); // mFolderMailingListPropertiesAction->setIcon(QIcon::fromTheme("document-properties-mailing-list")); mShowFolderShortcutDialogAction = new QAction(QIcon::fromTheme(QStringLiteral("configure-shortcuts")), i18n("&Assign Shortcut..."), this); actionCollection()->addAction(QStringLiteral("folder_shortcut_command"), mShowFolderShortcutDialogAction); connect(mShowFolderShortcutDialogAction, &QAction::triggered, mManageShowCollectionProperties, &ManageShowCollectionProperties::slotShowFolderShortcutDialog); // FIXME: this action is not currently enabled in the rc file, but even if // it were there is inconsistency between the action name and action. // "Expiration Settings" implies that this will lead to a settings dialogue // and it should be followed by a "...", but slotExpireFolder() performs // an immediate expiry. // // TODO: expire action should be disabled if there is no content or if // the folder can't delete messages. // // Leaving the action here for the moment, it and the "Expire" option in the // folder popup menu should be combined or at least made consistent. Same for // slotExpireFolder() and FolderViewItem::slotShowExpiryProperties(). mExpireFolderAction = new QAction(i18n("&Expiration Settings"), this); actionCollection()->addAction(QStringLiteral("expire"), mExpireFolderAction); connect(mExpireFolderAction, &QAction::triggered, this, &KMMainWidget::slotExpireFolder); mAkonadiStandardActionManager->interceptAction(Akonadi::StandardMailActionManager::MoveToTrash); connect(mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveToTrash), &QAction::triggered, this, &KMMainWidget::slotTrashSelectedMessages); mAkonadiStandardActionManager->interceptAction(Akonadi::StandardMailActionManager::MoveAllToTrash); connect(mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash), &QAction::triggered, this, &KMMainWidget::slotEmptyFolder); mAkonadiStandardActionManager->interceptAction(Akonadi::StandardActionManager::DeleteCollections); connect(mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections), &QAction::triggered, this, &KMMainWidget::slotRemoveFolder); // ### PORT ME: Add this to the context menu. Not possible right now because // the context menu uses XMLGUI, and that would add the entry to // all collection context menus mArchiveFolderAction = new QAction(i18n("&Archive Folder..."), this); actionCollection()->addAction(QStringLiteral("archive_folder"), mArchiveFolderAction); connect(mArchiveFolderAction, &QAction::triggered, this, &KMMainWidget::slotArchiveFolder); mDisplayMessageFormatMenu = new DisplayMessageFormatActionMenu(this); connect(mDisplayMessageFormatMenu, &DisplayMessageFormatActionMenu::changeDisplayMessageFormat, this, &KMMainWidget::slotChangeDisplayMessageFormat); actionCollection()->addAction(QStringLiteral("display_format_message"), mDisplayMessageFormatMenu); mPreferHtmlLoadExtAction = new KToggleAction(i18n("Load E&xternal References"), this); actionCollection()->addAction(QStringLiteral("prefer_html_external_refs"), mPreferHtmlLoadExtAction); connect(mPreferHtmlLoadExtAction, &KToggleAction::triggered, this, &KMMainWidget::slotOverrideHtmlLoadExt); { QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyCollections); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_C)); } { QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::Paste); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_V)); } { QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyItems); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::CTRL + Qt::Key_C)); } { QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CutItems); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::CTRL + Qt::Key_X)); } { QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyItemToMenu); action->setText(i18n("Copy Message To...")); action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::MoveItemToMenu); action->setText(i18n("Move Message To...")); } //----- Message Menu { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Message..."), this); actionCollection()->addAction(QStringLiteral("new_message"), action); action->setIconText(i18nc("@action:intoolbar New Empty Message", "New")); connect(action, &QAction::triggered, this, &KMMainWidget::slotCompose); // do not set a New shortcut if kmail is a component if (kmkernel->xmlGuiInstanceName().isEmpty()) { actionCollection()->setDefaultShortcut(action, KStandardShortcut::openNew().first()); } } mTemplateMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Message From &Template"), actionCollection()); mTemplateMenu->setDelayed(true); actionCollection()->addAction(QStringLiteral("new_from_template"), mTemplateMenu); connect(mTemplateMenu->menu(), &QMenu::aboutToShow, this, &KMMainWidget::slotShowNewFromTemplate); connect(mTemplateMenu->menu(), &QMenu::triggered, this, &KMMainWidget::slotNewFromTemplate); mMessageNewList = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new-list")), i18n("New Message t&o Mailing-List..."), this); actionCollection()->addAction(QStringLiteral("post_message"), mMessageNewList); connect(mMessageNewList, &QAction::triggered, this, &KMMainWidget::slotPostToML); actionCollection()->setDefaultShortcut(mMessageNewList, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_N)); mSendAgainAction = new QAction(i18n("Send A&gain..."), this); actionCollection()->addAction(QStringLiteral("send_again"), mSendAgainAction); connect(mSendAgainAction, &QAction::triggered, this, &KMMainWidget::slotResendMsg); //----- Create filter actions mFilterMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("&Create Filter"), this); actionCollection()->addAction(QStringLiteral("create_filter"), mFilterMenu); connect(mFilterMenu, &QAction::triggered, this, &KMMainWidget::slotFilter); { QAction *action = new QAction(i18n("Filter on &Subject..."), this); actionCollection()->addAction(QStringLiteral("subject_filter"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotSubjectFilter); mFilterMenu->addAction(action); } { QAction *action = new QAction(i18n("Filter on &From..."), this); actionCollection()->addAction(QStringLiteral("from_filter"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotFromFilter); mFilterMenu->addAction(action); } { QAction *action = new QAction(i18n("Filter on &To..."), this); actionCollection()->addAction(QStringLiteral("to_filter"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotToFilter); mFilterMenu->addAction(action); } { QAction *action = new QAction(i18n("Filter on &Cc..."), this); actionCollection()->addAction(QStringLiteral("cc_filter"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotCcFilter); mFilterMenu->addAction(action); } mFilterMenu->addAction(mMsgActions->listFilterAction()); mNewMessageFromTemplateAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New Message From &Template"), this); actionCollection()->addAction(QStringLiteral("use_template"), mNewMessageFromTemplateAction); connect(mNewMessageFromTemplateAction, &QAction::triggered, this, &KMMainWidget::slotUseTemplate); actionCollection()->setDefaultShortcut(mNewMessageFromTemplateAction, QKeySequence(Qt::SHIFT + Qt::Key_N)); //----- "Mark Thread" submenu mThreadStatusMenu = new KActionMenu(i18n("Mark &Thread"), this); actionCollection()->addAction(QStringLiteral("thread_status"), mThreadStatusMenu); mMarkThreadAsReadAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-mark-read")), i18n("Mark Thread as &Read"), this); actionCollection()->addAction(QStringLiteral("thread_read"), mMarkThreadAsReadAction); connect(mMarkThreadAsReadAction, &QAction::triggered, this, &KMMainWidget::slotSetThreadStatusRead); KMail::Util::addQActionHelpText(mMarkThreadAsReadAction, i18n("Mark all messages in the selected thread as read")); mThreadStatusMenu->addAction(mMarkThreadAsReadAction); mMarkThreadAsUnreadAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-mark-unread")), i18n("Mark Thread as &Unread"), this); actionCollection()->addAction(QStringLiteral("thread_unread"), mMarkThreadAsUnreadAction); connect(mMarkThreadAsUnreadAction, &QAction::triggered, this, &KMMainWidget::slotSetThreadStatusUnread); KMail::Util::addQActionHelpText(mMarkThreadAsUnreadAction, i18n("Mark all messages in the selected thread as unread")); mThreadStatusMenu->addAction(mMarkThreadAsUnreadAction); mThreadStatusMenu->addSeparator(); //----- "Mark Thread" toggle actions mToggleThreadImportantAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-mark-important")), i18n("Mark Thread as &Important"), this); actionCollection()->addAction(QStringLiteral("thread_flag"), mToggleThreadImportantAction); connect(mToggleThreadImportantAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusImportant); mToggleThreadImportantAction->setCheckedState(KGuiItem(i18n("Remove &Important Thread Mark"))); mThreadStatusMenu->addAction(mToggleThreadImportantAction); mToggleThreadToActAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-mark-task")), i18n("Mark Thread as &Action Item"), this); actionCollection()->addAction(QStringLiteral("thread_toact"), mToggleThreadToActAction); connect(mToggleThreadToActAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusToAct); mToggleThreadToActAction->setCheckedState(KGuiItem(i18n("Remove &Action Item Thread Mark"))); mThreadStatusMenu->addAction(mToggleThreadToActAction); //------- "Watch and ignore thread" actions mWatchThreadAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-thread-watch")), i18n("&Watch Thread"), this); actionCollection()->addAction(QStringLiteral("thread_watched"), mWatchThreadAction); connect(mWatchThreadAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusWatched); mIgnoreThreadAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-thread-ignored")), i18n("&Ignore Thread"), this); actionCollection()->addAction(QStringLiteral("thread_ignored"), mIgnoreThreadAction); connect(mIgnoreThreadAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusIgnored); mThreadStatusMenu->addSeparator(); mThreadStatusMenu->addAction(mWatchThreadAction); mThreadStatusMenu->addAction(mIgnoreThreadAction); mSaveAttachmentsAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Save A&ttachments..."), this); actionCollection()->addAction(QStringLiteral("file_save_attachments"), mSaveAttachmentsAction); connect(mSaveAttachmentsAction, &QAction::triggered, this, &KMMainWidget::slotSaveAttachments); mMoveActionMenu = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::MoveItemToMenu); mCopyActionMenu = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyItemToMenu); mCopyDecryptedActionMenu = new KActionMenu(i18n("Copy Decrypted To..."), this); actionCollection()->addAction(QStringLiteral("copy_decrypted_to_menu"), mCopyDecryptedActionMenu); connect(mCopyDecryptedActionMenu->menu(), &QMenu::triggered, this, &KMMainWidget::slotCopyDecryptedTo); mApplyAllFiltersAction = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Appl&y All Filters"), this); actionCollection()->addAction(QStringLiteral("apply_filters"), mApplyAllFiltersAction); connect(mApplyAllFiltersAction, &QAction::triggered, this, &KMMainWidget::slotApplyFilters); actionCollection()->setDefaultShortcut(mApplyAllFiltersAction, QKeySequence(Qt::CTRL + Qt::Key_J)); mApplyFilterActionsMenu = new KActionMenu(i18n("A&pply Filter"), this); actionCollection()->addAction(QStringLiteral("apply_filter_actions"), mApplyFilterActionsMenu); { QAction *action = new QAction(i18nc("View->", "&Expand Thread / Group"), this); actionCollection()->addAction(QStringLiteral("expand_thread"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Period)); KMail::Util::addQActionHelpText(action, i18n("Expand the current thread or group")); connect(action, &QAction::triggered, this, &KMMainWidget::slotExpandThread); } { QAction *action = new QAction(i18nc("View->", "&Collapse Thread / Group"), this); actionCollection()->addAction(QStringLiteral("collapse_thread"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Comma)); KMail::Util::addQActionHelpText(action, i18n("Collapse the current thread or group")); connect(action, &QAction::triggered, this, &KMMainWidget::slotCollapseThread); } { QAction *action = new QAction(i18nc("View->", "Ex&pand All Threads"), this); actionCollection()->addAction(QStringLiteral("expand_all_threads"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Period)); KMail::Util::addQActionHelpText(action, i18n("Expand all threads in the current folder")); connect(action, &QAction::triggered, this, &KMMainWidget::slotExpandAllThreads); } { QAction *action = new QAction(i18nc("View->", "C&ollapse All Threads"), this); actionCollection()->addAction(QStringLiteral("collapse_all_threads"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Comma)); KMail::Util::addQActionHelpText(action, i18n("Collapse all threads in the current folder")); connect(action, &QAction::triggered, this, &KMMainWidget::slotCollapseAllThreads); } QAction *dukeOfMonmoth = new QAction(i18n("&Display Message"), this); actionCollection()->addAction(QStringLiteral("display_message"), dukeOfMonmoth); connect(dukeOfMonmoth, &QAction::triggered, this, &KMMainWidget::slotDisplayCurrentMessage); actionCollection()->setDefaultShortcuts(dukeOfMonmoth, QList { QKeySequence(Qt::Key_Enter), QKeySequence(Qt::Key_Return) }); //----- Go Menu { QAction *action = new QAction(i18n("&Next Message"), this); actionCollection()->addAction(QStringLiteral("go_next_message"), action); actionCollection()->setDefaultShortcuts(action, QList { QKeySequence(Qt::Key_N), QKeySequence(Qt::Key_Right) }); KMail::Util::addQActionHelpText(action, i18n("Go to the next message")); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectNextMessage); } { QAction *action = new QAction(i18n("Next &Unread Message"), this); actionCollection()->addAction(QStringLiteral("go_next_unread_message"), action); actionCollection()->setDefaultShortcuts(action, QList { QKeySequence(Qt::Key_Plus), QKeySequence(Qt::Key_Plus + Qt::KeypadModifier) }); if (QApplication::isRightToLeft()) { action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); } else { action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); } action->setIconText(i18nc("@action:inmenu Goto next unread message", "Next")); KMail::Util::addQActionHelpText(action, i18n("Go to the next unread message")); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectNextUnreadMessage); } { QAction *action = new QAction(i18n("&Previous Message"), this); actionCollection()->addAction(QStringLiteral("go_prev_message"), action); KMail::Util::addQActionHelpText(action, i18n("Go to the previous message")); actionCollection()->setDefaultShortcuts(action, QList { QKeySequence(Qt::Key_P), QKeySequence(Qt::Key_Left) }); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectPreviousMessage); } { QAction *action = new QAction(i18n("Previous Unread &Message"), this); actionCollection()->addAction(QStringLiteral("go_prev_unread_message"), action); actionCollection()->setDefaultShortcuts(action, QList { QKeySequence(Qt::Key_Minus), QKeySequence(Qt::Key_Minus + Qt::KeypadModifier) }); if (QApplication::isRightToLeft()) { action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); } else { action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); } action->setIconText(i18nc("@action:inmenu Goto previous unread message.", "Previous")); KMail::Util::addQActionHelpText(action, i18n("Go to the previous unread message")); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectPreviousUnreadMessage); } { QAction *action = new QAction(i18n("Next Unread &Folder"), this); actionCollection()->addAction(QStringLiteral("go_next_unread_folder"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotNextUnreadFolder); actionCollection()->setDefaultShortcuts(action, QList { QKeySequence(Qt::ALT + Qt::Key_Plus), QKeySequence(Qt::ALT + Qt::Key_Plus + Qt::KeypadModifier) }); KMail::Util::addQActionHelpText(action, i18n("Go to the next folder with unread messages")); } { QAction *action = new QAction(i18n("Previous Unread F&older"), this); actionCollection()->addAction(QStringLiteral("go_prev_unread_folder"), action); actionCollection()->setDefaultShortcuts(action, QList { QKeySequence(Qt::ALT + Qt::Key_Minus), QKeySequence(Qt::ALT + Qt::Key_Minus + Qt::KeypadModifier) }); KMail::Util::addQActionHelpText(action, i18n("Go to the previous folder with unread messages")); connect(action, &QAction::triggered, this, &KMMainWidget::slotPrevUnreadFolder); } { QAction *action = new QAction(i18nc("Go->", "Next Unread &Text"), this); actionCollection()->addAction(QStringLiteral("go_next_unread_text"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Space)); KMail::Util::addQActionHelpText(action, i18n("Go to the next unread text")); action->setWhatsThis(i18n("Scroll down current message. " "If at end of current message, " "go to next unread message.")); connect(action, &QAction::triggered, this, &KMMainWidget::slotReadOn); } //----- Settings Menu { QAction *action = new QAction(i18n("Configure &Filters..."), this); action->setMenuRole(QAction::NoRole); // do not move to application menu on OS X actionCollection()->addAction(QStringLiteral("filter"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotFilter); } { QAction *action = new QAction(i18n("Manage &Sieve Scripts..."), this); actionCollection()->addAction(QStringLiteral("sieveFilters"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotManageSieveScripts); } { QAction *action = new QAction(i18n("&Add Account..."), this); actionCollection()->addAction(QStringLiteral("accountWizard"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotAccountWizard); } { mShowIntroductionAction = new QAction(QIcon::fromTheme(QStringLiteral("kmail")), i18n("KMail &Introduction"), this); actionCollection()->addAction(QStringLiteral("help_kmail_welcomepage"), mShowIntroductionAction); KMail::Util::addQActionHelpText(mShowIntroductionAction, i18n("Display KMail's Welcome Page")); connect(mShowIntroductionAction, &QAction::triggered, this, &KMMainWidget::slotIntro); mShowIntroductionAction->setEnabled(mMsgView != nullptr); } // ----- Standard Actions // KStandardAction::configureNotifications(this, SLOT(slotEditNotifications()), actionCollection()); { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("preferences-desktop-notification")), i18n("Configure &Notifications..."), this); action->setMenuRole(QAction::NoRole); // do not move to application menu on OS X actionCollection()->addAction(QStringLiteral("kmail_configure_notifications"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotEditNotifications); } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("&Configure KMail..."), this); action->setMenuRole(QAction::PreferencesRole); // this one should move to the application menu on OS X actionCollection()->addAction(QStringLiteral("kmail_configure_kmail"), action); connect(action, &QAction::triggered, kmkernel, &KMKernel::slotShowConfigurationDialog); } { mExpireConfigAction = new QAction(i18n("Expire..."), this); actionCollection()->addAction(QStringLiteral("expire_settings"), mExpireConfigAction); connect(mExpireConfigAction, &QAction::triggered, mManageShowCollectionProperties, &ManageShowCollectionProperties::slotShowExpiryProperties); } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add Favorite Folder..."), this); actionCollection()->addAction(QStringLiteral("add_favorite_folder"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotAddFavoriteFolder); } { mServerSideSubscription = new QAction(QIcon::fromTheme(QStringLiteral("folder-bookmarks")), i18n("Serverside Subscription..."), this); actionCollection()->addAction(QStringLiteral("serverside_subscription"), mServerSideSubscription); connect(mServerSideSubscription, &QAction::triggered, this, &KMMainWidget::slotServerSideSubscription); } { mApplyAllFiltersFolderAction = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Apply All Filters"), this); actionCollection()->addAction(QStringLiteral("apply_filters_folder"), mApplyAllFiltersFolderAction); connect(mServerSideSubscription, &QAction::triggered, this, [this] { slotApplyFiltersOnFolder(/* recursive */ false); }); } { mApplyAllFiltersFolderRecursiveAction = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Apply All Filters"), this); actionCollection()->addAction(QStringLiteral("apply_filters_folder_recursive"), mApplyAllFiltersFolderRecursiveAction); connect(mServerSideSubscription, &QAction::triggered, this, [this] { slotApplyFiltersOnFolder(/* recursive */ true); }); } { mApplyFilterFolderActionsMenu = new KActionMenu(i18n("Apply Filters on Folder"), this); actionCollection()->addAction(QStringLiteral("apply_filters_on_folder_actions"), mApplyFilterFolderActionsMenu); } { mApplyFilterFolderRecursiveActionsMenu = new KActionMenu(i18n("Apply Filters on Folder and all its Subfolders"), this); actionCollection()->addAction(QStringLiteral("apply_filters_on_folder_recursive_actions"), mApplyFilterFolderRecursiveActionsMenu); } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("kontact")), i18n("Import/Export KMail Data..."), this); actionCollection()->addAction(QStringLiteral("kmail_export_data"), action); connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotExportData); } { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("contact-new")), i18n("New AddressBook Contact..."), this); actionCollection()->addAction(QStringLiteral("kmail_new_addressbook_contact"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotCreateAddressBookContact); } actionCollection()->addAction(KStandardAction::Undo, QStringLiteral("kmail_undo"), this, SLOT(slotUndo())); menutimer = new QTimer(this); menutimer->setObjectName(QStringLiteral("menutimer")); menutimer->setSingleShot(true); connect(menutimer, &QTimer::timeout, this, &KMMainWidget::updateMessageActionsDelayed); connect(kmkernel->undoStack(), &KMail::UndoStack::undoStackChanged, this, &KMMainWidget::slotUpdateUndo); updateMessageActions(); updateFolderMenu(); mTagActionManager = new KMail::TagActionManager(this, actionCollection(), mMsgActions, mGUIClient); mFolderShortcutActionManager = new KMail::FolderShortcutActionManager(this, actionCollection()); { QAction *action = new QAction(i18n("Copy Message to Folder"), this); actionCollection()->addAction(QStringLiteral("copy_message_to_folder"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotCopySelectedMessagesToFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_C)); } { QAction *action = new QAction(i18n("Jump to Folder..."), this); actionCollection()->addAction(QStringLiteral("jump_to_folder"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotJumpToFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_J)); } { QAction *action = new QAction(i18n("Abort Current Operation"), this); actionCollection()->addAction(QStringLiteral("cancel"), action); connect(action, &QAction::triggered, ProgressManager::instance(), &KPIM::ProgressManager::slotAbortAll); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Escape)); } { QAction *action = new QAction(i18n("Focus on Next Folder"), this); actionCollection()->addAction(QStringLiteral("inc_current_folder"), action); connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusNextFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Right)); } { QAction *action = new QAction(i18n("Focus on Previous Folder"), this); actionCollection()->addAction(QStringLiteral("dec_current_folder"), action); connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusPrevFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Left)); } { QAction *action = new QAction(i18n("Select Folder with Focus"), this); actionCollection()->addAction(QStringLiteral("select_current_folder"), action); connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotSelectFocusFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Space)); } { QAction *action = new QAction(i18n("Focus on First Folder"), this); actionCollection()->addAction(QStringLiteral("focus_first_folder"), action); connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusFirstFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Home)); } { QAction *action = new QAction(i18n("Focus on Last Folder"), this); actionCollection()->addAction(QStringLiteral("focus_last_folder"), action); connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusLastFolder); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_End)); } { QAction *action = new QAction(i18n("Focus on Next Message"), this); actionCollection()->addAction(QStringLiteral("inc_current_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotFocusOnNextMessage); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Right)); } { QAction *action = new QAction(i18n("Focus on Previous Message"), this); actionCollection()->addAction(QStringLiteral("dec_current_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotFocusOnPrevMessage); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Left)); } { QAction *action = new QAction(i18n("Select First Message"), this); actionCollection()->addAction(QStringLiteral("select_first_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectFirstMessage); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Home)); } { QAction *action = new QAction(i18n("Select Last Message"), this); actionCollection()->addAction(QStringLiteral("select_last_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectLastMessage); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_End)); } { QAction *action = new QAction(i18n("Select Message with Focus"), this); actionCollection()->addAction(QStringLiteral("select_current_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectFocusedMessage); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Space)); } { mQuickSearchAction = new QAction(i18n("Set Focus to Quick Search"), this); //If change shortcut change Panel::setQuickSearchClickMessage(...) message actionCollection()->setDefaultShortcut(mQuickSearchAction, QKeySequence(Qt::ALT + Qt::Key_Q)); actionCollection()->addAction(QStringLiteral("focus_to_quickseach"), mQuickSearchAction); connect(mQuickSearchAction, &QAction::triggered, this, &KMMainWidget::slotFocusQuickSearch); updateQuickSearchLineText(); } { QAction *action = new QAction(i18n("Extend Selection to Previous Message"), this); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Left)); actionCollection()->addAction(QStringLiteral("previous_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotExtendSelectionToPreviousMessage); } { QAction *action = new QAction(i18n("Extend Selection to Next Message"), this); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Right)); actionCollection()->addAction(QStringLiteral("next_message"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotExtendSelectionToNextMessage); } { mMoveMsgToFolderAction = new QAction(i18n("Move Message to Folder"), this); actionCollection()->setDefaultShortcut(mMoveMsgToFolderAction, QKeySequence(Qt::Key_M)); actionCollection()->addAction(QStringLiteral("move_message_to_folder"), mMoveMsgToFolderAction); connect(mMoveMsgToFolderAction, &QAction::triggered, this, &KMMainWidget::slotMoveSelectedMessageToFolder); } mArchiveAction = new QAction(i18nc("@action", "Archive"), this); actionCollection()->addAction(QStringLiteral("archive_mails"), mArchiveAction); connect(mArchiveAction, &QAction::triggered, this, &KMMainWidget::slotArchiveMails); mUseLessBandwidth = new KToggleAction(i18n("Use Less Bandwidth"), this); actionCollection()->addAction(QStringLiteral("low_bandwidth"), mUseLessBandwidth); connect(mUseLessBandwidth, &KToggleAction::triggered, this, &KMMainWidget::slotBandwidth); mMarkAllMessageAsReadAndInAllSubFolder = new QAction(i18n("Mark All Messages As Read in This Folder and All its Subfolder"), this); mMarkAllMessageAsReadAndInAllSubFolder->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); actionCollection()->addAction(QStringLiteral("markallmessagereadcurentfolderandsubfolder"), mMarkAllMessageAsReadAndInAllSubFolder); connect(mMarkAllMessageAsReadAndInAllSubFolder, &KToggleAction::triggered, this, &KMMainWidget::slotMarkAllMessageAsReadInCurrentFolderAndSubfolder); mRemoveDuplicateRecursiveAction = new QAction(i18n("Remove Duplicates in This Folder and All its Subfolder"), this); actionCollection()->addAction(QStringLiteral("remove_duplicate_recursive"), mRemoveDuplicateRecursiveAction); connect(mRemoveDuplicateRecursiveAction, &KToggleAction::triggered, this, &KMMainWidget::slotRemoveDuplicateRecursive); } void KMMainWidget::slotAddFavoriteFolder() { if (!mFavoritesModel) { return; } QPointer dialog(selectFromAllFoldersDialog()); dialog->setWindowTitle(i18n("Add Favorite Folder")); if (dialog->exec() && dialog) { const Akonadi::Collection collection = dialog->selectedCollection(); if (collection.isValid()) { mFavoritesModel->addCollection(collection); } } } //----------------------------------------------------------------------------- void KMMainWidget::slotEditNotifications() { QPointer notifyDlg = new KMail::KMKnotify(this); notifyDlg->exec(); delete notifyDlg; } //----------------------------------------------------------------------------- void KMMainWidget::slotReadOn() { if (!mMsgView) { return; } mMsgView->viewer()->atBottom(); } void KMMainWidget::slotPageIsScrolledToBottom(bool isAtBottom) { if (isAtBottom) { slotSelectNextUnreadMessage(); } else { mMsgView->viewer()->slotJumpDown(); } } void KMMainWidget::slotNextUnreadFolder() { if (!mFolderTreeWidget) { return; } mGoToFirstUnreadMessageInSelectedFolder = true; mFolderTreeWidget->folderTreeView()->selectNextUnreadFolder(); mGoToFirstUnreadMessageInSelectedFolder = false; } void KMMainWidget::slotPrevUnreadFolder() { if (!mFolderTreeWidget) { return; } mGoToFirstUnreadMessageInSelectedFolder = true; mFolderTreeWidget->folderTreeView()->selectPrevUnreadFolder(); mGoToFirstUnreadMessageInSelectedFolder = false; } void KMMainWidget::slotExpandThread() { mMessagePane->setCurrentThreadExpanded(true); } void KMMainWidget::slotCollapseThread() { mMessagePane->setCurrentThreadExpanded(false); } void KMMainWidget::slotExpandAllThreads() { // TODO: Make this asynchronous ? (if there is enough demand) #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif mMessagePane->setAllThreadsExpanded(true); } void KMMainWidget::slotCollapseAllThreads() { // TODO: Make this asynchronous ? (if there is enough demand) #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif mMessagePane->setAllThreadsExpanded(false); } //----------------------------------------------------------------------------- void KMMainWidget::updateMessageMenu() { updateMessageActions(); } void KMMainWidget::startUpdateMessageActionsTimer() { // FIXME: This delay effectively CAN make the actions to be in an incoherent state // Maybe we should mark actions as "dirty" here and check it in every action handler... updateMessageActions(true); menutimer->stop(); menutimer->start(500); } void KMMainWidget::updateMessageActions(bool fast) { Akonadi::Item::List selectedItems; Akonadi::Item::List selectedVisibleItems; bool allSelectedBelongToSameThread = false; if (mCurrentFolderSettings && mCurrentFolderSettings->isValid() && mMessagePane->getSelectionStats(selectedItems, selectedVisibleItems, &allSelectedBelongToSameThread) ) { mMsgActions->setCurrentMessage(mMessagePane->currentItem(), selectedVisibleItems); } else { mMsgActions->setCurrentMessage(Akonadi::Item()); } if (!fast) { updateMessageActionsDelayed(); } } void KMMainWidget::updateMessageActionsDelayed() { int count; Akonadi::Item::List selectedItems; Akonadi::Item::List selectedVisibleItems; bool allSelectedBelongToSameThread = false; Akonadi::Item currentMessage; bool currentFolderSettingsIsValid = mCurrentFolderSettings && mCurrentFolderSettings->isValid(); if (currentFolderSettingsIsValid && mMessagePane->getSelectionStats(selectedItems, selectedVisibleItems, &allSelectedBelongToSameThread) ) { count = selectedItems.count(); currentMessage = mMessagePane->currentItem(); } else { count = 0; currentMessage = Akonadi::Item(); } mApplyFilterActionsMenu->setEnabled(currentFolderSettingsIsValid); mApplyFilterFolderRecursiveActionsMenu->setEnabled(currentFolderSettingsIsValid); // // Here we have: // // - A list of selected messages stored in selectedSernums. // The selected messages might contain some invisible ones as a selected // collapsed node "includes" all the children in the selection. // - A list of selected AND visible messages stored in selectedVisibleSernums. // This list does not contain children of selected and collapsed nodes. // // Now, actions can operate on: // - Any set of messages // These are called "mass actions" and are enabled whenever we have a message selected. // In fact we should differentiate between actions that operate on visible selection // and those that operate on the selection as a whole (without considering visibility)... // - A single thread // These are called "thread actions" and are enabled whenever all the selected messages belong // to the same thread. If the selection doesn't cover the whole thread then the action // will act on the whole thread anyway (thus will silently extend the selection) // - A single message // And we have two sub-cases: // - The selection must contain exactly one message // These actions can't ignore the hidden messages and thus must be disabled if // the selection contains any. // - The selection must contain exactly one visible message // These actions will ignore the hidden message and thus can be enabled if // the selection contains any. // bool readOnly = currentFolderSettingsIsValid && (mCurrentFolderSettings->rights() & Akonadi::Collection::ReadOnly); // can we apply strictly single message actions ? (this is false if the whole selection contains more than one message) bool single_actions = count == 1; // can we apply loosely single message actions ? (this is false if the VISIBLE selection contains more than one message) bool singleVisibleMessageSelected = selectedVisibleItems.count() == 1; // can we apply "mass" actions to the selection ? (this is actually always true if the selection is non-empty) bool mass_actions = count >= 1; // does the selection identify a single thread ? bool thread_actions = mass_actions && allSelectedBelongToSameThread && mMessagePane->isThreaded(); // can we apply flags to the selected messages ? bool flags_available = KMailSettings::self()->allowLocalFlags() || !(currentFolderSettingsIsValid ? readOnly : true); mThreadStatusMenu->setEnabled(thread_actions); // these need to be handled individually, the user might have them // in the toolbar mWatchThreadAction->setEnabled(thread_actions && flags_available); mIgnoreThreadAction->setEnabled(thread_actions && flags_available); mMarkThreadAsReadAction->setEnabled(thread_actions); mMarkThreadAsUnreadAction->setEnabled(thread_actions); mToggleThreadToActAction->setEnabled(thread_actions && flags_available); mToggleThreadImportantAction->setEnabled(thread_actions && flags_available); bool canDeleteMessages = currentFolderSettingsIsValid && (mCurrentFolderSettings->rights() & Akonadi::Collection::CanDeleteItem); mTrashThreadAction->setEnabled(thread_actions && canDeleteMessages); mDeleteThreadAction->setEnabled(thread_actions && canDeleteMessages); if (messageView() && messageView()->viewer() && messageView()->viewer()->headerStylePlugin()) { messageView()->viewer()->headerStylePlugin()->headerStyle()->setReadOnlyMessage(!canDeleteMessages); } if (currentMessage.isValid()) { MessageStatus status; status.setStatusFromFlags(currentMessage.flags()); mTagActionManager->updateActionStates(count, mMessagePane->currentItem()); if (thread_actions) { mToggleThreadToActAction->setChecked(status.isToAct()); mToggleThreadImportantAction->setChecked(status.isImportant()); mWatchThreadAction->setChecked(status.isWatched()); mIgnoreThreadAction->setChecked(status.isIgnored()); } } mMoveActionMenu->setEnabled(mass_actions && canDeleteMessages); if (mMoveMsgToFolderAction) { mMoveMsgToFolderAction->setEnabled(mass_actions && canDeleteMessages); } //mCopyActionMenu->setEnabled( mass_actions ); mDeleteAction->setEnabled(mass_actions && canDeleteMessages); mExpireConfigAction->setEnabled(canDeleteMessages); if (mMsgView) { mMsgView->findInMessageAction()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection)); } mMsgActions->forwardInlineAction()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection)); mMsgActions->forwardAttachedAction()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection)); mMsgActions->forwardMenu()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection)); mMsgActions->editAction()->setEnabled(single_actions); mNewMessageFromTemplateAction->setEnabled(single_actions && CommonKernel->folderIsTemplates(mCurrentCollection)); filterMenu()->setEnabled(single_actions); mMsgActions->redirectAction()->setEnabled(/*single_actions &&*/ mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection)); if (mMsgActions->customTemplatesMenu()) { mMsgActions->customTemplatesMenu()->forwardActionMenu()->setEnabled(mass_actions); mMsgActions->customTemplatesMenu()->replyActionMenu()->setEnabled(single_actions); mMsgActions->customTemplatesMenu()->replyAllActionMenu()->setEnabled(single_actions); } // "Print" will act on the current message: it will ignore any hidden selection mMsgActions->printAction()->setEnabled(singleVisibleMessageSelected); // "Print preview" will act on the current message: it will ignore any hidden selection QAction *printPreviewAction = mMsgActions->printPreviewAction(); if (printPreviewAction) { printPreviewAction->setEnabled(singleVisibleMessageSelected); } // "View Source" will act on the current message: it will ignore any hidden selection if (mMsgView) { mMsgView->viewSourceAction()->setEnabled(singleVisibleMessageSelected); mMsgView->selectAllAction()->setEnabled(count); } MessageStatus status; status.setStatusFromFlags(currentMessage.flags()); QList< QAction *> actionList; bool statusSendAgain = single_actions && ((currentMessage.isValid() && status.isSent()) || (currentMessage.isValid() && CommonKernel->folderIsSentMailFolder(mCurrentCollection))); if (statusSendAgain) { actionList << mSendAgainAction; } else if (single_actions) { actionList << messageActions()->editAction(); } actionList << mSaveAttachmentsAction; if (mCurrentCollection.isValid() && FolderArchive::FolderArchiveUtil::resourceSupportArchiving(mCurrentCollection.resource())) { actionList << mArchiveAction; } mGUIClient->unplugActionList(QStringLiteral("messagelist_actionlist")); mGUIClient->plugActionList(QStringLiteral("messagelist_actionlist"), actionList); mSendAgainAction->setEnabled(statusSendAgain); mSaveAsAction->setEnabled(mass_actions); if (currentFolderSettingsIsValid) { updateMoveAction(mCurrentFolderSettings->statistics()); } else { updateMoveAction(false); } const auto col = CommonKernel->collectionFromId(CommonKernel->outboxCollectionFolder().id()); const qint64 nbMsgOutboxCollection = col.statistics().count(); mSendQueued->setEnabled(nbMsgOutboxCollection > 0); mSendActionMenu->setEnabled(nbMsgOutboxCollection > 0); const bool newPostToMailingList = mCurrentFolderSettings && mCurrentFolderSettings->isMailingListEnabled(); mMessageNewList->setEnabled(newPostToMailingList); slotUpdateOnlineStatus(static_cast(KMailSettings::self()->networkState())); if (QAction *act = action(QStringLiteral("kmail_undo"))) { act->setEnabled(!kmkernel->undoStack()->isEmpty()); } // Enable / disable all filters. for (QAction *filterAction : qAsConst(mFilterMenuActions)) { filterAction->setEnabled(count > 0); } mApplyAllFiltersAction->setEnabled(count); mApplyFilterActionsMenu->setEnabled(count); mSelectAllMessages->setEnabled(count); if (currentMessage.hasFlag(Akonadi::MessageFlags::Encrypted) || count > 1) { mCopyDecryptedActionMenu->setVisible(true); mAkonadiStandardActionManager->standardActionManager()->createActionFolderMenu( mCopyDecryptedActionMenu->menu(), Akonadi::StandardActionManager::CopyItemToMenu); } else { mCopyDecryptedActionMenu->setVisible(false); } } void KMMainWidget::slotAkonadiStandardActionUpdated() { if (mCollectionProperties) { if (mCurrentCollection.isValid()) { const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(mCurrentCollection.resource()); mCollectionProperties->setEnabled(!mCurrentFolderSettings->isStructural() && (instance.status() != Akonadi::AgentInstance::Broken)); } else { mCollectionProperties->setEnabled(false); } QList< QAction * > collectionProperties; if (mCollectionProperties->isEnabled()) { collectionProperties << mCollectionProperties; } mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_collectionproperties_actionlist")); mGUIClient->plugActionList(QStringLiteral("akonadi_collection_collectionproperties_actionlist"), collectionProperties); } const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural(); if (mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections)) { mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections)->setEnabled(mCurrentFolderSettings && (mCurrentCollection.rights() & Collection::CanDeleteCollection) && !mCurrentFolderSettings->isSystemFolder() && folderWithContent); } if (mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)) { mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)->setEnabled(folderWithContent && (mCurrentFolderSettings->count() > 0) && mCurrentFolderSettings->canDeleteMessages()); mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)->setText((mCurrentFolderSettings && CommonKernel->folderIsTrash(mCurrentCollection)) ? i18n("E&mpty Trash") : i18n( "&Move All Messages to Trash")); } QList< QAction * > addToFavorite; QAction *actionAddToFavoriteCollections = akonadiStandardAction(Akonadi::StandardActionManager::AddToFavoriteCollections); if (actionAddToFavoriteCollections) { if (mEnableFavoriteFolderView && actionAddToFavoriteCollections->isEnabled()) { addToFavorite << actionAddToFavoriteCollections; } mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_add_to_favorites_actionlist")); mGUIClient->plugActionList(QStringLiteral("akonadi_collection_add_to_favorites_actionlist"), addToFavorite); } QList< QAction * > syncActionList; QAction *actionSync = akonadiStandardAction(Akonadi::StandardActionManager::SynchronizeCollections); if (actionSync && actionSync->isEnabled()) { syncActionList << actionSync; } actionSync = akonadiStandardAction(Akonadi::StandardActionManager::SynchronizeCollectionsRecursive); if (actionSync && actionSync->isEnabled()) { syncActionList << actionSync; } mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_sync_actionlist")); mGUIClient->plugActionList(QStringLiteral("akonadi_collection_sync_actionlist"), syncActionList); QList< QAction * > actionList; QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CreateCollection); if (action && action->isEnabled()) { actionList << action; } action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::MoveCollectionToMenu); if (action && action->isEnabled()) { actionList << action; } action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyCollectionToMenu); if (action && action->isEnabled()) { actionList << action; } mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_move_copy_menu_actionlist")); mGUIClient->plugActionList(QStringLiteral("akonadi_collection_move_copy_menu_actionlist"), actionList); } void KMMainWidget::updateHtmlMenuEntry() { if (mDisplayMessageFormatMenu && mPreferHtmlLoadExtAction) { // the visual ones only make sense if we are showing a message list const bool enabledAction = (mFolderTreeWidget && mFolderTreeWidget->folderTreeView()->currentFolder().isValid()); mDisplayMessageFormatMenu->setEnabled(enabledAction); const bool isEnabled = (mFolderTreeWidget && mFolderTreeWidget->folderTreeView()->currentFolder().isValid()); const bool useHtml = (mFolderDisplayFormatPreference == MessageViewer::Viewer::Html || (mHtmlGlobalSetting && mFolderDisplayFormatPreference == MessageViewer::Viewer::UseGlobalSetting)); mPreferHtmlLoadExtAction->setEnabled(isEnabled && useHtml); mDisplayMessageFormatMenu->setDisplayMessageFormat(mFolderDisplayFormatPreference); mPreferHtmlLoadExtAction->setChecked((mHtmlLoadExtGlobalSetting ? !mFolderHtmlLoadExtPreference : mFolderHtmlLoadExtPreference)); } } //----------------------------------------------------------------------------- void KMMainWidget::updateFolderMenu() { if (!CommonKernel->outboxCollectionFolder().isValid()) { QTimer::singleShot(1000, this, &KMMainWidget::updateFolderMenu); return; } const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural() && mCurrentFolderSettings->isValid(); mFolderMailingListPropertiesAction->setEnabled(folderWithContent && !mCurrentFolderSettings->isSystemFolder()); QList< QAction * > actionlist; if (mCurrentCollection.id() == CommonKernel->outboxCollectionFolder().id() && (mCurrentCollection).statistics().count() > 0) { qCDebug(KMAIL_LOG) << "Enabling send queued"; mSendQueued->setEnabled(true); actionlist << mSendQueued; } // if ( mCurrentCollection.id() != CommonKernel->trashCollectionFolder().id() ) { // actionlist << mTrashAction; // } mGUIClient->unplugActionList(QStringLiteral("outbox_folder_actionlist")); mGUIClient->plugActionList(QStringLiteral("outbox_folder_actionlist"), actionlist); actionlist.clear(); const bool isASearchFolder = mCurrentCollection.resource() == QLatin1String("akonadi_search_resource"); if (isASearchFolder) { mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections)->setText(i18n("&Delete Search")); } mArchiveFolderAction->setEnabled(mCurrentFolderSettings && folderWithContent); bool isInTrashFolder = (mCurrentFolderSettings && CommonKernel->folderIsTrash(mCurrentCollection)); QAction *moveToTrash = akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash); akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash)->setText(isInTrashFolder ? i18nc("@action Hard delete, bypassing trash", "&Delete") : i18n("&Move to Trash")); akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash)->setIcon(isInTrashFolder ? QIcon::fromTheme(QStringLiteral("edit-delete")) : QIcon::fromTheme(QStringLiteral("user-trash"))); //Use same text as in Text property. Change it in kf5 moveToTrash->setToolTip(isInTrashFolder ? i18nc("@action Hard delete, bypassing trash", "&Delete") : i18n("&Move to Trash")); mTrashThreadAction->setIcon(isInTrashFolder ? QIcon::fromTheme(QStringLiteral("edit-delete")) : QIcon::fromTheme(QStringLiteral("user-trash"))); mTrashThreadAction->setText(isInTrashFolder ? i18n("Delete T&hread") : i18n("M&ove Thread to Trash")); mSearchMessages->setText((mCurrentCollection.resource() == QLatin1String("akonadi_search_resource")) ? i18n("Edit Search...") : i18n("&Find Messages...")); mExpireConfigAction->setEnabled(mCurrentFolderSettings && !mCurrentFolderSettings->isStructural() && mCurrentFolderSettings->canDeleteMessages() && folderWithContent && !MailCommon::Util::isVirtualCollection(mCurrentCollection)); updateHtmlMenuEntry(); mShowFolderShortcutDialogAction->setEnabled(folderWithContent); actionlist << akonadiStandardAction(Akonadi::StandardActionManager::ManageLocalSubscriptions); bool imapFolderIsOnline = false; if (mCurrentFolderSettings && PimCommon::MailUtil::isImapFolder(mCurrentCollection, imapFolderIsOnline)) { if (imapFolderIsOnline) { actionlist << mServerSideSubscription; } } mGUIClient->unplugActionList(QStringLiteral("collectionview_actionlist")); mGUIClient->plugActionList(QStringLiteral("collectionview_actionlist"), actionlist); const bool folderIsValid = folderWithContent; mApplyAllFiltersFolderAction->setEnabled(folderIsValid); mApplyFilterFolderActionsMenu->setEnabled(folderIsValid); mApplyFilterFolderRecursiveActionsMenu->setEnabled(folderIsValid); for (auto a : qAsConst(mFilterFolderMenuActions)) { a->setEnabled(folderIsValid); } for (auto a : qAsConst(mFilterFolderMenuRecursiveActions)) { a->setEnabled(folderIsValid); } } //----------------------------------------------------------------------------- void KMMainWidget::slotIntro() { if (!mMsgView) { return; } mMsgView->clear(true); // hide widgets that are in the way: if (mMessagePane && mLongFolderList) { mMessagePane->hide(); } mMsgView->displayAboutPage(); clearCurrentFolder(); } void KMMainWidget::slotShowStartupFolder() { connect(MailCommon::FilterManager::instance(), &FilterManager::filtersChanged, this, &KMMainWidget::initializeFilterActions); // Plug various action lists. This can't be done in the constructor, as that is called before // the main window or Kontact calls createGUI(). // This function however is called with a single shot timer. checkAkonadiServerManagerState(); mFolderShortcutActionManager->createActions(); mTagActionManager->createActions(); messageActions()->setupForwardingActionsList(mGUIClient); initializePluginActions(); QString newFeaturesMD5 = KMReaderWin::newFeaturesMD5(); if (kmkernel->firstStart() || KMailSettings::self()->previousNewFeaturesMD5() != newFeaturesMD5) { KMailSettings::self()->setPreviousNewFeaturesMD5(newFeaturesMD5); slotIntro(); } } void KMMainWidget::checkAkonadiServerManagerState() { Akonadi::ServerManager::State state = Akonadi::ServerManager::self()->state(); if (state == Akonadi::ServerManager::Running) { initializeFilterActions(); } else { connect(Akonadi::ServerManager::self(), &ServerManager::stateChanged, this, &KMMainWidget::slotServerStateChanged); } } void KMMainWidget::slotServerStateChanged(Akonadi::ServerManager::State state) { if (state == Akonadi::ServerManager::Running) { initializeFilterActions(); disconnect(Akonadi::ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State))); } } QList KMMainWidget::actionCollections() const { return QList() << actionCollection(); } //----------------------------------------------------------------------------- void KMMainWidget::slotUpdateUndo() { if (actionCollection()->action(QStringLiteral("kmail_undo"))) { QAction *act = actionCollection()->action(QStringLiteral("kmail_undo")); act->setEnabled(!kmkernel->undoStack()->isEmpty()); const QString infoStr = kmkernel->undoStack()->undoInfo(); if (infoStr.isEmpty()) { act->setText(i18n("&Undo")); } else { act->setText(i18n("&Undo: \"%1\"", kmkernel->undoStack()->undoInfo())); } } } //----------------------------------------------------------------------------- void KMMainWidget::clearFilterActions() { if (mGUIClient->factory()) { if (!mFilterTBarActions.isEmpty()) { mGUIClient->unplugActionList(QStringLiteral("toolbar_filter_actions")); } if (!mFilterMenuActions.isEmpty()) { mGUIClient->unplugActionList(QStringLiteral("menu_filter_actions")); } if (!mFilterFolderMenuActions.isEmpty()) { mGUIClient->unplugActionList(QStringLiteral("menu_filter_folder_actions")); } if (!mFilterFolderMenuRecursiveActions.isEmpty()) { mGUIClient->unplugActionList(QStringLiteral("menu_filter_folder_recursive_actions")); } } for (QAction *a : qAsConst(mFilterMenuActions)) { actionCollection()->removeAction(a); } for (QAction *a : qAsConst(mFilterFolderMenuActions)) { actionCollection()->removeAction(a); } for (QAction *a : qAsConst(mFilterFolderMenuRecursiveActions)) { actionCollection()->removeAction(a); } mApplyFilterActionsMenu->menu()->clear(); mApplyFilterFolderActionsMenu->menu()->clear(); mApplyFilterFolderRecursiveActionsMenu->menu()->clear(); mFilterTBarActions.clear(); mFilterMenuActions.clear(); mFilterFolderMenuActions.clear(); mFilterFolderMenuRecursiveActions.clear(); qDeleteAll(mFilterCommands); mFilterCommands.clear(); } void KMMainWidget::initializePluginActions() { KMailPluginInterface::self()->initializePluginActions(QStringLiteral("kmail"), mGUIClient); } QAction *KMMainWidget::filterToAction(MailCommon::MailFilter *filter) { QString displayText = i18n("Filter %1", filter->name()); QString icon = filter->icon(); if (icon.isEmpty()) { icon = QStringLiteral("system-run"); } QAction *filterAction = new QAction(QIcon::fromTheme(icon), displayText, actionCollection()); filterAction->setProperty("filter_id", filter->identifier()); filterAction->setIconText(filter->toolbarName()); // The shortcut configuration is done in the filter dialog. // The shortcut set in the shortcut dialog would not be saved back to // the filter settings correctly. actionCollection()->setShortcutsConfigurable(filterAction, false); return filterAction; } //----------------------------------------------------------------------------- void KMMainWidget::initializeFilterActions() { clearFilterActions(); mApplyFilterActionsMenu->menu()->addAction(mApplyAllFiltersAction); mApplyFilterFolderActionsMenu->menu()->addAction(mApplyAllFiltersFolderAction); mApplyFilterFolderRecursiveActionsMenu->menu()->addAction(mApplyAllFiltersFolderRecursiveAction); bool addedSeparator = false; const QList lstFilters = MailCommon::FilterManager::instance()->filters(); for (MailFilter *filter : lstFilters) { if (!filter->isEmpty() && filter->configureShortcut() && filter->isEnabled()) { QString filterName = QStringLiteral("Filter %1").arg(filter->name()); QString normalizedName = filterName.replace(QLatin1Char(' '), QLatin1Char('_')); if (action(normalizedName)) { continue; } if (!addedSeparator) { QAction *a = mApplyFilterActionsMenu->menu()->addSeparator(); mFilterMenuActions.append(a); a = mApplyFilterFolderActionsMenu->menu()->addSeparator(); mFilterFolderMenuActions.append(a); a = mApplyFilterFolderRecursiveActionsMenu->menu()->addSeparator(); mFilterFolderMenuRecursiveActions.append(a); addedSeparator = true; } KMMetaFilterActionCommand *filterCommand = new KMMetaFilterActionCommand(filter->identifier(), this); mFilterCommands.append(filterCommand); auto filterAction = filterToAction(filter); actionCollection()->addAction(normalizedName, filterAction); connect(filterAction, &QAction::triggered, filterCommand, &KMMetaFilterActionCommand::start); actionCollection()->setDefaultShortcut(filterAction, filter->shortcut()); mApplyFilterActionsMenu->menu()->addAction(filterAction); mFilterMenuActions.append(filterAction); if (filter->configureToolbar()) { mFilterTBarActions.append(filterAction); } filterAction = filterToAction(filter); actionCollection()->addAction(normalizedName + QStringLiteral("___folder"), filterAction); connect(filterAction, &QAction::triggered, this, [this] { slotApplyFilterOnFolder(/* recursive */ false); }); mApplyFilterFolderActionsMenu->menu()->addAction(filterAction); mFilterFolderMenuActions.append(filterAction); filterAction = filterToAction(filter); actionCollection()->addAction(normalizedName + QStringLiteral("___folder_recursive"), filterAction); connect(filterAction, &QAction::triggered, this, [this] { slotApplyFilterOnFolder(/* recursive */ true); }); mApplyFilterFolderRecursiveActionsMenu->menu()->addAction(filterAction); mFilterFolderMenuRecursiveActions.append(filterAction); } } if (mGUIClient->factory()) { if (!mFilterMenuActions.isEmpty()) { mGUIClient->plugActionList(QStringLiteral("menu_filter_actions"), mFilterMenuActions); } if (!mFilterTBarActions.isEmpty()) { mFilterTBarActions.prepend(mToolbarActionSeparator); mGUIClient->plugActionList(QStringLiteral("toolbar_filter_actions"), mFilterTBarActions); } if (!mFilterFolderMenuActions.isEmpty()) { mGUIClient->plugActionList(QStringLiteral("menu_filter_folder_actions"), mFilterFolderMenuActions); } if (!mFilterFolderMenuRecursiveActions.isEmpty()) { mGUIClient->plugActionList(QStringLiteral("menu_filter_folder_recursive_actions"), mFilterFolderMenuRecursiveActions); } } // Our filters have changed, now enable/disable them updateMessageActions(); } void KMMainWidget::updateFileMenu() { const bool isEmpty = MailCommon::Util::agentInstances().isEmpty(); actionCollection()->action(QStringLiteral("check_mail"))->setEnabled(!isEmpty); actionCollection()->action(QStringLiteral("check_mail_in"))->setEnabled(!isEmpty); } //----------------------------------------------------------------------------- const KMMainWidget::PtrList *KMMainWidget::mainWidgetList() { // better safe than sorry; check whether the global static has already been destroyed if (theMainWidgetList.isDestroyed()) { return nullptr; } return theMainWidgetList; } QSharedPointer KMMainWidget::currentFolder() const { return mCurrentFolderSettings; } QAction *KMMainWidget::action(const QString &name) { return mActionCollection->action(name); } KActionMenu *KMMainWidget::filterMenu() const { return mFilterMenu; } KActionMenu *KMMainWidget::mailingListActionMenu() const { return mMsgActions->mailingListActionMenu(); } QAction *KMMainWidget::editAction() const { return mMsgActions->editAction(); } QAction *KMMainWidget::sendAgainAction() const { return mSendAgainAction; } QAction *KMMainWidget::sendQueuedAction() const { return mSendQueued; } KActionMenuTransport *KMMainWidget::sendQueueViaMenu() const { return mSendActionMenu; } KMail::MessageActions *KMMainWidget::messageActions() const { return mMsgActions; } //----------------------------------------------------------------------------- QString KMMainWidget::overrideEncoding() const { if (mMsgView) { return mMsgView->overrideEncoding(); } else { return MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); } } void KMMainWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); mWasEverShown = true; } KActionCollection *KMMainWidget::actionCollection() const { return mActionCollection; } void KMMainWidget::slotRequestFullSearchFromQuickSearch() { // First, open the search window. If we are currently on a search folder, // the search associated with that will be loaded. if (!slotSearch()) { return; } assert(mSearchWin); // Now we look at the current state of the quick search, and if there's // something in there, we add the criteria to the existing search for // the search folder, if applicable, or make a new one from it. SearchPattern pattern; const QString searchString = mMessagePane->currentFilterSearchString(); if (!searchString.isEmpty()) { MessageList::Core::QuickSearchLine::SearchOptions options = mMessagePane->currentOptions(); QByteArray searchStringVal; if (options & MessageList::Core::QuickSearchLine::SearchEveryWhere) { searchStringVal = QByteArrayLiteral(""); } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstSubject) { searchStringVal = QByteArrayLiteral("subject"); } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstBody) { searchStringVal = QByteArrayLiteral(""); } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstFrom) { searchStringVal = QByteArrayLiteral("from"); } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstBcc) { searchStringVal = QByteArrayLiteral("bcc"); } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstTo) { searchStringVal = QByteArrayLiteral("to"); } else { searchStringVal = QByteArrayLiteral(""); } pattern.append(SearchRule::createInstance(searchStringVal, SearchRule::FuncContains, searchString)); const QList statusList = mMessagePane->currentFilterStatus(); for (MessageStatus status : statusList) { if (status.hasAttachment()) { pattern.append(SearchRule::createInstance(searchStringVal, SearchRule::FuncHasAttachment, searchString)); status.setHasAttachment(false); } if (!status.isOfUnknownStatus()) { pattern.append(SearchRule::Ptr(new SearchRuleStatus(status))); } } } if (!pattern.isEmpty()) { mSearchWin->addRulesToSearchPattern(pattern); } } void KMMainWidget::updateVacationScriptStatus(bool active, const QString &serverName) { mVacationScriptIndicator->setVacationScriptActive(active, serverName); mVacationIndicatorActive = mVacationScriptIndicator->hasVacationScriptActive(); } QWidget *KMMainWidget::vacationScriptIndicator() const { return mVacationScriptIndicator; } void KMMainWidget::updateVacationScriptStatus() { updateVacationScriptStatus(mVacationIndicatorActive); } FolderTreeView *KMMainWidget::folderTreeView() const { return mFolderTreeWidget->folderTreeView(); } KXMLGUIClient *KMMainWidget::guiClient() const { return mGUIClient; } KMail::TagActionManager *KMMainWidget::tagActionManager() const { return mTagActionManager; } KMail::FolderShortcutActionManager *KMMainWidget::folderShortcutActionManager() const { return mFolderShortcutActionManager; } void KMMainWidget::slotMessageSelected(const Akonadi::Item &item) { delete mShowBusySplashTimer; mShowBusySplashTimer = nullptr; if (mMsgView) { // The current selection was cleared, so we'll remove the previously // selected message from the preview pane if (!item.isValid()) { mMsgView->clear(); } else { mShowBusySplashTimer = new QTimer(this); mShowBusySplashTimer->setSingleShot(true); connect(mShowBusySplashTimer, &QTimer::timeout, this, &KMMainWidget::slotShowBusySplash); mShowBusySplashTimer->start(1000); Akonadi::ItemFetchJob *itemFetchJob = mMsgView->viewer()->createFetchJob(item); if (mCurrentCollection.isValid()) { const QString resource = mCurrentCollection.resource(); itemFetchJob->setProperty("_resource", QVariant::fromValue(resource)); connect(itemFetchJob, &ItemFetchJob::itemsReceived, this, &KMMainWidget::itemsReceived); connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMMainWidget::itemsFetchDone); } } } } void KMMainWidget::itemsReceived(const Akonadi::Item::List &list) { Q_ASSERT(list.size() == 1); delete mShowBusySplashTimer; mShowBusySplashTimer = nullptr; if (!mMsgView) { return; } Item item = list.first(); if (mMessagePane) { mMessagePane->show(); if (mMessagePane->currentItem() != item) { // The user has selected another email already, so don't render this one. // Mark it as read, though, if the user settings say so. if (MessageViewer::MessageViewerSettings::self()->delayedMarkAsRead() && MessageViewer::MessageViewerSettings::self()->delayedMarkTime() == 0) { item.setFlag(Akonadi::MessageFlags::Seen); Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(item, this); modifyJob->disableRevisionCheck(); modifyJob->setIgnorePayload(true); } return; } } Akonadi::Item copyItem(item); if (mCurrentCollection.isValid()) { copyItem.setParentCollection(mCurrentCollection); } mMsgView->setMessage(copyItem); // reset HTML override to the folder setting mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference); mMsgView->setHtmlLoadExtOverride(mFolderHtmlLoadExtPreference); mMsgView->setDecryptMessageOverwrite(false); mMsgActions->setCurrentMessage(copyItem); } void KMMainWidget::itemsFetchDone(KJob *job) { delete mShowBusySplashTimer; mShowBusySplashTimer = nullptr; if (job->error()) { // Unfortunately job->error() is Job::Unknown in many cases. // (see JobPrivate::handleResponse in akonadi/job.cpp) // So we show the "offline" page after checking the resource status. qCDebug(KMAIL_LOG) << job->error() << job->errorString(); const QString resource = job->property("_resource").toString(); const Akonadi::AgentInstance agentInstance = Akonadi::AgentManager::self()->instance(resource); if (!agentInstance.isOnline()) { // The resource is offline mMessagePane->show(); if (mMsgView) { mMsgView->viewer()->enableMessageDisplay(); mMsgView->clear(true); if (kmkernel->isOffline()) { showOfflinePage(); } else { showResourceOfflinePage(); } } } else { // Some other error showMessageActivities(job->errorString()); } } } QAction *KMMainWidget::akonadiStandardAction(Akonadi::StandardActionManager::Type type) { return mAkonadiStandardActionManager->action(type); } QAction *KMMainWidget::akonadiStandardAction(Akonadi::StandardMailActionManager::Type type) { return mAkonadiStandardActionManager->action(type); } StandardMailActionManager *KMMainWidget::standardMailActionManager() const { return mAkonadiStandardActionManager; } void KMMainWidget::slotRemoveDuplicates() { RemoveDuplicateMailJob *job = new RemoveDuplicateMailJob(mFolderTreeWidget->folderTreeView()->selectionModel(), this, this); job->start(); } void KMMainWidget::slotServerSideSubscription() { if (!mCurrentCollection.isValid()) { return; } PimCommon::ManageServerSideSubscriptionJob *job = new PimCommon::ManageServerSideSubscriptionJob(this); job->setCurrentCollection(mCurrentCollection); job->setParentWidget(this); job->start(); } void KMMainWidget::savePaneSelection() { if (mMessagePane) { mMessagePane->saveCurrentSelection(); } } void KMMainWidget::updatePaneTagComboBox() { if (mMessagePane) { mMessagePane->updateTagComboBox(); } } void KMMainWidget::slotCreateAddressBookContact() { CreateNewContactJob *job = new CreateNewContactJob(this, this); job->start(); } void KMMainWidget::slotOpenRecentMsg(const QUrl &url) { KMOpenMsgCommand *openCommand = new KMOpenMsgCommand(this, url, overrideEncoding(), this); openCommand->start(); } void KMMainWidget::addRecentFile(const QUrl &mUrl) { mOpenRecentAction->addUrl(mUrl); KConfigGroup grp = mConfig->group(QStringLiteral("Recent Files")); mOpenRecentAction->saveEntries(grp); grp.sync(); } void KMMainWidget::slotMoveMessageToTrash() { if (messageView() && messageView()->viewer() && mCurrentCollection.isValid()) { KMTrashMsgCommand *command = new KMTrashMsgCommand(mCurrentCollection, messageView()->viewer()->messageItem(), -1); command->start(); } } void KMMainWidget::slotArchiveMails() { if (mCurrentCollection.isValid()) { const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList(); KMKernel::self()->folderArchiveManager()->setArchiveItems(selectedMessages, mCurrentCollection.resource()); } } void KMMainWidget::updateQuickSearchLineText() { //If change change shortcut mMessagePane->setQuickSearchClickMessage(i18nc("Show shortcut for focus quick search. Don't change it", "Search...<%1>", mQuickSearchAction->shortcut().toString())); } void KMMainWidget::slotChangeDisplayMessageFormat(MessageViewer::Viewer::DisplayFormatMessage format) { if (format == MessageViewer::Viewer::Html) { const int result = KMessageBox::warningContinueCancel(this, // the warning text is taken from configuredialog.cpp: i18n("Use of HTML in mail will make you more vulnerable to " "\"spam\" and may increase the likelihood that your system will be " "compromised by other present and anticipated security exploits."), i18n("Security Warning"), KGuiItem(i18n("Use HTML")), KStandardGuiItem::cancel(), QStringLiteral("OverrideHtmlWarning"), nullptr); if (result == KMessageBox::Cancel) { mDisplayMessageFormatMenu->setDisplayMessageFormat(MessageViewer::Viewer::Text); return; } } mFolderDisplayFormatPreference = format; //Update mPrefererHtmlLoadExtAction const bool useHtml = (mFolderDisplayFormatPreference == MessageViewer::Viewer::Html || (mHtmlGlobalSetting && mFolderDisplayFormatPreference == MessageViewer::Viewer::UseGlobalSetting)); mPreferHtmlLoadExtAction->setEnabled(useHtml); if (mMsgView) { mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference); mMsgView->update(true); } writeFolderConfig(); } void KMMainWidget::populateMessageListStatusFilterCombo() { mMessagePane->populateStatusFilterCombo(); } void KMMainWidget::slotCollectionRemoved(const Akonadi::Collection &col) { if (mFavoritesModel) { mFavoritesModel->removeCollection(col); } } void KMMainWidget::slotMarkAllMessageAsReadInCurrentFolderAndSubfolder() { if (mCurrentCollection.isValid()) { MarkAllMessagesAsReadInFolderAndSubFolderJob *job = new MarkAllMessagesAsReadInFolderAndSubFolderJob(this); job->setTopLevelCollection(mCurrentCollection); job->start(); } } void KMMainWidget::slotRemoveDuplicateRecursive() { if (mCurrentCollection.isValid()) { RemoveDuplicateMessageInFolderAndSubFolderJob *job = new RemoveDuplicateMessageInFolderAndSubFolderJob(this); job->setTopLevelCollection(mCurrentCollection); job->start(); } } void KMMainWidget::slotUpdateConfig() { readFolderConfig(); updateHtmlMenuEntry(); } void KMMainWidget::printCurrentMessage(bool preview) { bool result = false; if (messageView() && messageView()->viewer()) { if (MessageViewer::MessageViewerSettings::self()->printSelectedText()) { result = messageView()->printSelectedText(preview); } } if (!result) { const bool useFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont(); const QString overrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); const Akonadi::Item currentItem = messageView()->viewer()->messageItem(); KMPrintCommand *command = new KMPrintCommand(this, currentItem, messageView()->viewer()->headerStylePlugin(), messageView()->viewer()->displayFormatMessageOverwrite(), messageView()->viewer()->htmlLoadExternal(), useFixedFont, overrideEncoding); command->setPrintPreview(preview); command->start(); } } void KMMainWidget::slotRedirectCurrentMessage() { if (messageView() && messageView()->viewer()) { const Akonadi::Item currentItem = messageView()->viewer()->messageItem(); if (!currentItem.hasPayload()) { return; } KMCommand *command = new KMRedirectCommand(this, currentItem); command->start(); } } void KMMainWidget::replyCurrentMessageCommand(MessageComposer::ReplyStrategy strategy) { if (messageView() && messageView()->viewer()) { Akonadi::Item currentItem = messageView()->viewer()->messageItem(); if (!currentItem.hasPayload()) { return; } const QString text = messageView()->copyText(); KMReplyCommand *command = new KMReplyCommand(this, currentItem, strategy, text); command->start(); } } void KMMainWidget::slotReplyMessageTo(const KMime::Message::Ptr &message, bool replyToAll) { Akonadi::Item item; item.setPayload(message); item.setMimeType(KMime::Message::mimeType()); KMReplyCommand *command = new KMReplyCommand(this, item, replyToAll ? MessageComposer::ReplyAll : MessageComposer::ReplyAuthor); command->start(); } void KMMainWidget::showMessageActivities(const QString &str) { BroadcastStatus::instance()->setStatusMsg(str); PimCommon::LogActivitiesManager::self()->appendLog(str); } void KMMainWidget::slotCopyDecryptedTo(QAction *action) { if (action) { const auto index = action->data().toModelIndex(); const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); auto command = new KMCopyDecryptedCommand(collection, mMessagePane->selectionAsMessageItemList()); command->start(); } } diff --git a/src/kmreadermainwin.cpp b/src/kmreadermainwin.cpp index 1b4d47817..b2754806e 100644 --- a/src/kmreadermainwin.cpp +++ b/src/kmreadermainwin.cpp @@ -1,649 +1,649 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2011-2017 Montel Laurent KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // // A toplevel KMainWindow derived class for displaying // single messages or single message parts. // // Could be extended to include support for normal main window // widgets like a toolbar. #include "kmreadermainwin.h" #include "kmreaderwin.h" #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include "kmcommands.h" #include #include #include "kmmainwidget.h" #include #include #include "messageactions.h" #include "util.h" #include "mailcommon/mailkernel.h" #include #include "messageviewer/headerstyleplugin.h" #include "messageviewer/headerstyle.h" #include #include #include #include #include #include #include #include #include #include #include #include "kpimtextedit/texttospeech.h" #include using namespace MailCommon; KMReaderMainWin::KMReaderMainWin(MessageViewer::Viewer::DisplayFormatMessage format, bool htmlLoadExtOverride, const QString &name) : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#")) { mReaderWin = new KMReaderWin(this, this, actionCollection()); mReaderWin->setDisplayFormatMessageOverwrite(format); mReaderWin->setHtmlLoadExtOverride(htmlLoadExtOverride); mReaderWin->setDecryptMessageOverwrite(true); initKMReaderMainWin(); } KMReaderMainWin::KMReaderMainWin(const QString &name) : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#")) { mReaderWin = new KMReaderWin(this, this, actionCollection()); initKMReaderMainWin(); } KMReaderMainWin::KMReaderMainWin(KMime::Content *aMsgPart, MessageViewer::Viewer::DisplayFormatMessage format, const QString &encoding, const QString &name) : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#")) { mReaderWin = new KMReaderWin(this, this, actionCollection()); mReaderWin->setOverrideEncoding(encoding); mReaderWin->setDisplayFormatMessageOverwrite(format); mReaderWin->setMsgPart(aMsgPart); initKMReaderMainWin(); } void KMReaderMainWin::initKMReaderMainWin() { setCentralWidget(mReaderWin); setupAccel(); setupGUI(Keys | StatusBar | Create, QStringLiteral("kmreadermainwin.rc")); mMsgActions->setupForwardingActionsList(this); applyMainWindowSettings(KMKernel::self()->config()->group("Separate Reader Window")); if (!mReaderWin->message().isValid()) { menuBar()->hide(); toolBar(QStringLiteral("mainToolBar"))->hide(); } connect(kmkernel, &KMKernel::configChanged, this, &KMReaderMainWin::slotConfigChanged); connect(mReaderWin, &KMReaderWin::showStatusBarMessage, this, &KMReaderMainWin::slotShowMessageStatusBar); } KMReaderMainWin::~KMReaderMainWin() { KConfigGroup grp(KMKernel::self()->config()->group("Separate Reader Window")); saveMainWindowSettings(grp); } void KMReaderMainWin::slotShowMessageStatusBar(const QString &msg) { statusBar()->showMessage(msg); } void KMReaderMainWin::setUseFixedFont(bool useFixedFont) { mReaderWin->setUseFixedFont(useFixedFont); } MessageViewer::Viewer *KMReaderMainWin::viewer() const { return mReaderWin->viewer(); } void KMReaderMainWin::showMessage(const QString &encoding, const Akonadi::Item &msg, const Akonadi::Collection &parentCollection) { mParentCollection = parentCollection; mReaderWin->setOverrideEncoding(encoding); mReaderWin->setMessage(msg, MimeTreeParser::Force); KMime::Message::Ptr message = MessageComposer::Util::message(msg); QString caption; if (message) { caption = message->subject()->asUnicodeString(); } if (mParentCollection.isValid()) { caption += QLatin1String(" - "); caption += MailCommon::Util::fullCollectionPath(mParentCollection); } if (!caption.isEmpty()) { setCaption(caption); } mMsg = msg; mMsgActions->setCurrentMessage(msg); const bool canChange = mParentCollection.isValid() ? (bool)(mParentCollection.rights() & Akonadi::Collection::CanDeleteItem) : false; mTrashAction->setEnabled(canChange); if (mReaderWin->viewer() && mReaderWin->viewer()->headerStylePlugin() && mReaderWin->viewer()->headerStylePlugin()->headerStyle()) { mReaderWin->viewer()->headerStylePlugin()->headerStyle()->setReadOnlyMessage(!canChange); } menuBar()->show(); toolBar(QStringLiteral("mainToolBar"))->show(); } void KMReaderMainWin::showMessage(const QString &encoding, const KMime::Message::Ptr &message) { if (!message) { return; } Akonadi::Item item; item.setPayload(message); Akonadi::MessageFlags::copyMessageFlags(*message, item); item.setMimeType(KMime::Message::mimeType()); mMsg = item; mMsgActions->setCurrentMessage(item); mReaderWin->setOverrideEncoding(encoding); mReaderWin->setMessage(message); setCaption(message->subject()->asUnicodeString()); mTrashAction->setEnabled(false); menuBar()->show(); toolBar(QStringLiteral("mainToolBar"))->show(); } void KMReaderMainWin::slotReplyOrForwardFinished() { if (KMailSettings::self()->closeAfterReplyOrForward()) { close(); } } Akonadi::Collection KMReaderMainWin::parentCollection() const { if (mParentCollection.isValid()) { return mParentCollection; } else { return mMsg.parentCollection(); } } void KMReaderMainWin::slotTrashMessage() { if (!mMsg.isValid()) { return; } KMTrashMsgCommand *command = new KMTrashMsgCommand(parentCollection(), mMsg, -1); command->start(); close(); } void KMReaderMainWin::slotForwardInlineMsg() { if (!mReaderWin->message().hasPayload()) { return; } KMCommand *command = nullptr; const Akonadi::Collection parentCol = mReaderWin->message().parentCollection(); if (parentCol.isValid()) { QSharedPointer fd = FolderSettings::forCollection(parentCol, false); if (fd) { command = new KMForwardCommand(this, mReaderWin->message(), fd->identity(), QString(), mReaderWin->copyText()); } else { command = new KMForwardCommand(this, mReaderWin->message(), 0, QString(), mReaderWin->copyText()); } } else { command = new KMForwardCommand(this, mReaderWin->message(), 0, QString(), mReaderWin->copyText()); } connect(command, &KMTrashMsgCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished); command->start(); } void KMReaderMainWin::slotForwardAttachedMessage() { if (!mReaderWin->message().hasPayload()) { return; } KMCommand *command = nullptr; const Akonadi::Collection parentCol = mReaderWin->message().parentCollection(); if (parentCol.isValid()) { QSharedPointer fd = FolderSettings::forCollection(parentCol, false); if (fd) { command = new KMForwardAttachedCommand(this, mReaderWin->message(), fd->identity()); } else { command = new KMForwardAttachedCommand(this, mReaderWin->message()); } } else { command = new KMForwardAttachedCommand(this, mReaderWin->message()); } connect(command, &KMTrashMsgCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished); command->start(); } void KMReaderMainWin::slotRedirectMessage() { const Akonadi::Item currentItem = mReaderWin->message(); if (!currentItem.hasPayload()) { return; } KMRedirectCommand *command = new KMRedirectCommand(this, currentItem); connect(command, &KMRedirectCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished); command->start(); } void KMReaderMainWin::slotCustomReplyToMsg(const QString &tmpl) { const Akonadi::Item currentItem = mReaderWin->message(); if (!currentItem.hasPayload()) { return; } KMReplyCommand *command = new KMReplyCommand(this, currentItem, MessageComposer::ReplySmart, mReaderWin->copyText(), false, tmpl); connect(command, &KMReplyCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished); command->start(); } void KMReaderMainWin::slotCustomReplyAllToMsg(const QString &tmpl) { const Akonadi::Item currentItem = mReaderWin->message(); if (!currentItem.hasPayload()) { return; } KMReplyCommand *command = new KMReplyCommand(this, currentItem, MessageComposer::ReplyAll, mReaderWin->copyText(), false, tmpl); connect(command, &KMReplyCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished); command->start(); } void KMReaderMainWin::slotCustomForwardMsg(const QString &tmpl) { const Akonadi::Item currentItem = mReaderWin->message(); if (!currentItem.hasPayload()) { return; } KMForwardCommand *command = new KMForwardCommand(this, currentItem, 0, tmpl, mReaderWin->copyText()); connect(command, &KMForwardCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished); command->start(); } void KMReaderMainWin::slotConfigChanged() { //readConfig(); mMsgActions->setupForwardActions(actionCollection()); mMsgActions->setupForwardingActionsList(this); } void KMReaderMainWin::setupAccel() { if (!kmkernel->xmlGuiInstanceName().isEmpty()) { setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2")); } mMsgActions = new KMail::MessageActions(actionCollection(), this); mMsgActions->setMessageView(mReaderWin); connect(mMsgActions, &KMail::MessageActions::replyActionFinished, this, &KMReaderMainWin::slotReplyOrForwardFinished); //----- File Menu mSaveAtmAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Save A&ttachments..."), actionCollection()); connect(mSaveAtmAction, &QAction::triggered, mReaderWin->viewer(), &MessageViewer::Viewer::slotAttachmentSaveAll); mTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("user-trash")), i18n("&Move to Trash"), this); mTrashAction->setIconText(i18nc("@action:intoolbar Move to Trash", "Trash")); KMail::Util::addQActionHelpText(mTrashAction, i18n("Move message to trashcan")); actionCollection()->addAction(QStringLiteral("move_to_trash"), mTrashAction); actionCollection()->setDefaultShortcut(mTrashAction, QKeySequence(Qt::Key_Delete)); connect(mTrashAction, &QAction::triggered, this, &KMReaderMainWin::slotTrashMessage); QAction *closeAction = KStandardAction::close(this, &KMReaderMainWin::close, actionCollection()); QList closeShortcut = closeAction->shortcuts(); closeAction->setShortcuts(closeShortcut << QKeySequence(Qt::Key_Escape)); //----- Message Menu connect(mReaderWin->viewer(), &MessageViewer::Viewer::displayPopupMenu, this, &KMReaderMainWin::slotMessagePopup); connect(mReaderWin->viewer(), &MessageViewer::Viewer::itemRemoved, this, &QWidget::close); setStandardToolBarMenuEnabled(true); KStandardAction::configureToolbars(this, &KMReaderMainWin::slotEditToolbars, actionCollection()); connect(mReaderWin->viewer(), &MessageViewer::Viewer::moveMessageToTrash, this, &KMReaderMainWin::slotTrashMessage); } QAction *KMReaderMainWin::copyActionMenu(QMenu *menu) { KMMainWidget *mainwin = kmkernel->getKMMainWidget(); if (mainwin) { KActionMenu *action = new KActionMenu(menu); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); action->setText(i18n("Copy Message To...")); mainwin->standardMailActionManager()->standardActionManager()->createActionFolderMenu(action->menu(), Akonadi::StandardActionManager::CopyItemToMenu); connect(action->menu(), &QMenu::triggered, this, &KMReaderMainWin::slotCopyItem); return action; } return nullptr; } QAction *KMReaderMainWin::moveActionMenu(QMenu *menu) { KMMainWidget *mainwin = kmkernel->getKMMainWidget(); if (mainwin) { KActionMenu *action = new KActionMenu(menu); action->setText(i18n("Move Message To...")); mainwin->standardMailActionManager()->standardActionManager()->createActionFolderMenu(action->menu(), Akonadi::StandardActionManager::MoveItemToMenu); connect(action->menu(), &QMenu::triggered, this, &KMReaderMainWin::slotMoveItem); return action; } return nullptr; } void KMReaderMainWin::slotMoveItem(QAction *action) { if (action) { const QModelIndex index = action->data().toModelIndex(); const Akonadi::Collection collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); copyOrMoveItem(collection, true); } } void KMReaderMainWin::copyOrMoveItem(const Akonadi::Collection &collection, bool move) { if (mMsg.isValid()) { if (move) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(mMsg, collection, this); connect(job, &KJob::result, this, &KMReaderMainWin::slotCopyMoveResult); } else { Akonadi::ItemCopyJob *job = new Akonadi::ItemCopyJob(mMsg, collection, this); connect(job, &KJob::result, this, &KMReaderMainWin::slotCopyMoveResult); } } else { Akonadi::ItemCreateJob *job = new Akonadi::ItemCreateJob(mMsg, collection, this); connect(job, &KJob::result, this, &KMReaderMainWin::slotCopyMoveResult); } } void KMReaderMainWin::slotCopyItem(QAction *action) { if (action) { const QModelIndex index = action->data().toModelIndex(); const Akonadi::Collection collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); copyOrMoveItem(collection, false); } } void KMReaderMainWin::slotCopyMoveResult(KJob *job) { if (job->error()) { KMessageBox::sorry(this, i18n("Cannot copy item. %1", job->errorString())); } } void KMReaderMainWin::slotMessagePopup(const Akonadi::Item &aMsg, const WebEngineViewer::WebHitTestResult &result, const QPoint &aPoint) { QUrl aUrl = result.linkUrl(); QUrl imageUrl = result.imageUrl(); mMsg = aMsg; const QString email = KEmailAddress::firstEmailAddress(aUrl.path()).toLower(); if (aUrl.scheme() == QLatin1String("mailto") && !email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(this); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email, Akonadi::ContactSearchJob::ExactMatch); job->setProperty("msg", QVariant::fromValue(mMsg)); job->setProperty("point", aPoint); job->setProperty("imageUrl", imageUrl); job->setProperty("url", aUrl); job->setProperty("webhitresult", QVariant::fromValue(result)); connect(job, &Akonadi::ItemCopyJob::result, this, &KMReaderMainWin::slotContactSearchJobForMessagePopupDone); } else { showMessagePopup(mMsg, aUrl, imageUrl, aPoint, false, false, result); } } void KMReaderMainWin::slotContactSearchJobForMessagePopupDone(KJob *job) { const Akonadi::ContactSearchJob *searchJob = qobject_cast(job); const bool contactAlreadyExists = !searchJob->contacts().isEmpty(); const Akonadi::Item::List listContact = searchJob->items(); const bool uniqueContactFound = (listContact.count() == 1); if (uniqueContactFound) { mReaderWin->setContactItem(listContact.first(), searchJob->contacts().at(0)); } else { mReaderWin->clearContactItem(); } const Akonadi::Item msg = job->property("msg").value(); const QPoint aPoint = job->property("point").toPoint(); const QUrl imageUrl = job->property("imageUrl").toUrl(); const QUrl url = job->property("url").toUrl(); const WebEngineViewer::WebHitTestResult result = job->property("webhitresult").value(); showMessagePopup(msg, url, imageUrl, aPoint, contactAlreadyExists, uniqueContactFound, result); } void KMReaderMainWin::showMessagePopup(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &aPoint, bool contactAlreadyExists, bool uniqueContactFound, const WebEngineViewer::WebHitTestResult &result) { QMenu *menu = nullptr; bool urlMenuAdded = false; bool copyAdded = false; const bool messageHasPayload = msg.hasPayload(); if (!url.isEmpty()) { if (url.scheme() == QLatin1String("mailto")) { // popup on a mailto URL menu = new QMenu(this); menu->addAction(mReaderWin->mailToComposeAction()); if (messageHasPayload) { menu->addAction(mReaderWin->mailToReplyAction()); menu->addAction(mReaderWin->mailToForwardAction()); menu->addSeparator(); } if (contactAlreadyExists) { if (uniqueContactFound) { menu->addAction(mReaderWin->editContactAction()); } else { menu->addAction(mReaderWin->openAddrBookAction()); } } else { menu->addAction(mReaderWin->addAddrBookAction()); menu->addAction(mReaderWin->addToExistingContactAction()); } menu->addSeparator(); menu->addMenu(mReaderWin->viewHtmlOption()); menu->addSeparator(); menu->addAction(mReaderWin->copyURLAction()); copyAdded = true; urlMenuAdded = true; } else if (url.scheme() != QLatin1String("attachment")) { // popup on a not-mailto URL menu = new QMenu(this); menu->addAction(mReaderWin->urlOpenAction()); menu->addAction(mReaderWin->addBookmarksAction()); menu->addAction(mReaderWin->urlSaveAsAction()); menu->addAction(mReaderWin->copyURLAction()); menu->addSeparator(); menu->addAction(mReaderWin->shareServiceUrlMenu()); menu->addSeparator(); menu->addActions(mReaderWin->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedUrl)); if (!imageUrl.isEmpty()) { menu->addSeparator(); menu->addAction(mReaderWin->copyImageLocation()); menu->addAction(mReaderWin->downloadImageToDiskAction()); menu->addAction(mReaderWin->shareImage()); } urlMenuAdded = true; } } const QString selectedText(mReaderWin->copyText()); if (!selectedText.isEmpty()) { if (!menu) { menu = new QMenu(this); } if (urlMenuAdded) { menu->addSeparator(); } if (messageHasPayload) { menu->addAction(mMsgActions->replyMenu()); menu->addSeparator(); menu->addAction(mMsgActions->mailingListActionMenu()); menu->addSeparator(); } if (!copyAdded) { menu->addAction(mReaderWin->copyAction()); } menu->addAction(mReaderWin->selectAllAction()); menu->addSeparator(); mMsgActions->addWebShortcutsMenu(menu, selectedText); menu->addSeparator(); menu->addActions(mReaderWin->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedSelection)); if (KPIMTextEdit::TextToSpeech::self()->isReady()) { menu->addSeparator(); menu->addAction(mReaderWin->speakTextAction()); } } else if (!urlMenuAdded) { if (!menu) { menu = new QMenu(this); } // popup somewhere else (i.e., not a URL) on the message if (messageHasPayload) { bool replyForwardMenu = false; Akonadi::Collection col = parentCollection(); if (col.isValid()) { if (!(CommonKernel->folderIsSentMailFolder(col) || CommonKernel->folderIsDrafts(col) || CommonKernel->folderIsTemplates(col))) { replyForwardMenu = true; } } else if (messageHasPayload) { replyForwardMenu = true; } if (replyForwardMenu) { // add the reply and forward actions only if we are not in a sent-mail, // templates or drafts folder menu->addAction(mMsgActions->replyMenu()); menu->addAction(mMsgActions->forwardMenu()); menu->addSeparator(); } menu->addAction(copyActionMenu(menu)); menu->addAction(moveActionMenu(menu)); menu->addSeparator(); menu->addAction(mMsgActions->mailingListActionMenu()); menu->addSeparator(); if (!imageUrl.isEmpty()) { menu->addSeparator(); menu->addAction(mReaderWin->copyImageLocation()); menu->addAction(mReaderWin->downloadImageToDiskAction()); menu->addAction(mReaderWin->shareImage()); menu->addSeparator(); } menu->addAction(mMsgActions->printPreviewAction()); menu->addAction(mMsgActions->printAction()); menu->addSeparator(); menu->addAction(mReaderWin->saveAsAction()); menu->addAction(mSaveAtmAction); if (msg.isValid()) { menu->addSeparator(); menu->addActions(mReaderWin->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedMessage)); } } else { menu->addAction(mReaderWin->toggleFixFontAction()); if (!mReaderWin->mimePartTreeIsEmpty()) { menu->addAction(mReaderWin->toggleMimePartTreeAction()); } } if (msg.isValid()) { menu->addAction(mMsgActions->addFollowupReminderAction()); } if (msg.isValid()) { menu->addSeparator(); menu->addAction(mMsgActions->addFollowupReminderAction()); } - if (kmkernel->allowToDebugBalooSupport()) { + if (kmkernel->allowToDebugAkonadiSeachSupport()) { menu->addSeparator(); - menu->addAction(mMsgActions->debugBalooAction()); + menu->addAction(mMsgActions->debugAkonadiSearchAction()); } } if (!selectedText.isEmpty()) { const QList interceptorUrlActions = mReaderWin->interceptorUrlActions(result); if (!interceptorUrlActions.isEmpty()) { menu->addSeparator(); menu->addActions(interceptorUrlActions); } } if (menu) { KAcceleratorManager::manage(menu); menu->exec(aPoint, nullptr); delete menu; } } void KMReaderMainWin::slotEditToolbars() { KConfigGroup grp(KMKernel::self()->config(), "ReaderWindow"); saveMainWindowSettings(grp); QPointer dlg = new KEditToolBar(guiFactory(), this); connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMReaderMainWin::slotUpdateToolbars); dlg->exec(); delete dlg; } void KMReaderMainWin::slotUpdateToolbars() { createGUI(QStringLiteral("kmreadermainwin.rc")); applyMainWindowSettings(KConfigGroup(KMKernel::self()->config(), "ReaderWindow")); } diff --git a/src/messageactions.cpp b/src/messageactions.cpp index 667346d3f..e82e54358 100644 --- a/src/messageactions.cpp +++ b/src/messageactions.cpp @@ -1,714 +1,714 @@ /* Copyright (c) 2007 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messageactions.h" #include "settings/kmailsettings.h" #include "kmreaderwin.h" #include "kmkernel.h" #include "mailcommon/mailkernel.h" #include "kmmainwidget.h" #include "util.h" #include "kmcommands.h" #include #include #include #include "MessageCore/MailingList" #include #include "messageviewer/messageviewersettings.h" #include "messageviewer/headerstyleplugin.h" #include #include #include #include #include #include #include "messagecomposer/followupreminderselectdatedialog.h" #include "job/createfollowupreminderonexistingmessagejob.h" #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KMail; MessageActions::MessageActions(KActionCollection *ac, QWidget *parent) : QObject(parent) , mParent(parent) { mWebShortcutMenuManager = new KIO::KUriFilterSearchProviderActions(this); mReplyActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18nc("Message->", "&Reply"), this); ac->addAction(QStringLiteral("message_reply_menu"), mReplyActionMenu); connect(mReplyActionMenu, &KActionMenu::triggered, this, &MessageActions::slotReplyToMsg); mReplyAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("&Reply..."), this); ac->addAction(QStringLiteral("reply"), mReplyAction); ac->setDefaultShortcut(mReplyAction, Qt::Key_R); connect(mReplyAction, &QAction::triggered, this, &MessageActions::slotReplyToMsg); mReplyActionMenu->addAction(mReplyAction); mReplyAuthorAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("Reply to A&uthor..."), this); ac->addAction(QStringLiteral("reply_author"), mReplyAuthorAction); ac->setDefaultShortcut(mReplyAuthorAction, Qt::SHIFT + Qt::Key_A); connect(mReplyAuthorAction, &QAction::triggered, this, &MessageActions::slotReplyAuthorToMsg); mReplyActionMenu->addAction(mReplyAuthorAction); mReplyAllAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n("Reply to &All..."), this); ac->addAction(QStringLiteral("reply_all"), mReplyAllAction); ac->setDefaultShortcut(mReplyAllAction, Qt::Key_A); connect(mReplyAllAction, &QAction::triggered, this, &MessageActions::slotReplyAllToMsg); mReplyActionMenu->addAction(mReplyAllAction); mReplyListAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-list")), i18n("Reply to Mailing-&List..."), this); ac->addAction(QStringLiteral("reply_list"), mReplyListAction); ac->setDefaultShortcut(mReplyListAction, Qt::Key_L); connect(mReplyListAction, &QAction::triggered, this, &MessageActions::slotReplyListToMsg); mReplyActionMenu->addAction(mReplyListAction); mNoQuoteReplyAction = new QAction(i18n("Reply Without &Quote..."), this); ac->addAction(QStringLiteral("noquotereply"), mNoQuoteReplyAction); ac->setDefaultShortcut(mNoQuoteReplyAction, Qt::SHIFT + Qt::Key_R); connect(mNoQuoteReplyAction, &QAction::triggered, this, &MessageActions::slotNoQuoteReplyToMsg); mListFilterAction = new QAction(i18n("Filter on Mailing-&List..."), this); ac->addAction(QStringLiteral("mlist_filter"), mListFilterAction); connect(mListFilterAction, &QAction::triggered, this, &MessageActions::slotMailingListFilter); mStatusMenu = new KActionMenu(i18n("Mar&k Message"), this); ac->addAction(QStringLiteral("set_status"), mStatusMenu); KMMainWidget *mainwin = kmkernel->getKMMainWidget(); if (mainwin) { QAction *action = mainwin->akonadiStandardAction(Akonadi::StandardMailActionManager::MarkMailAsRead); mStatusMenu->addAction(action); action = mainwin->akonadiStandardAction(Akonadi::StandardMailActionManager::MarkMailAsUnread); mStatusMenu->addAction(action); mStatusMenu->addSeparator(); action = mainwin->akonadiStandardAction(Akonadi::StandardMailActionManager::MarkMailAsImportant); mStatusMenu->addAction(action); action = mainwin->akonadiStandardAction(Akonadi::StandardMailActionManager::MarkMailAsActionItem); mStatusMenu->addAction(action); } mEditAction = new QAction(QIcon::fromTheme(QStringLiteral("accessories-text-editor")), i18n("&Edit Message"), this); ac->addAction(QStringLiteral("edit"), mEditAction); connect(mEditAction, &QAction::triggered, this, &MessageActions::editCurrentMessage); ac->setDefaultShortcut(mEditAction, Qt::Key_T); mAnnotateAction = new QAction(QIcon::fromTheme(QStringLiteral("view-pim-notes")), i18n("Add Note..."), this); ac->addAction(QStringLiteral("annotate"), mAnnotateAction); connect(mAnnotateAction, &QAction::triggered, this, &MessageActions::annotateMessage); mPrintAction = KStandardAction::print(this, &MessageActions::slotPrintMessage, ac); mPrintPreviewAction = KStandardAction::printPreview(this, &MessageActions::slotPrintPreviewMsg, ac); mForwardActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("Message->", "&Forward"), this); ac->addAction(QStringLiteral("message_forward"), mForwardActionMenu); mForwardAttachedAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu Message->Forward->", "As &Attachment..."), this); connect(mForwardAttachedAction, SIGNAL(triggered(bool)), parent, SLOT(slotForwardAttachedMessage())); ac->addAction(QStringLiteral("message_forward_as_attachment"), mForwardAttachedAction); mForwardInlineAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu Message->Forward->", "&Inline..."), this); connect(mForwardInlineAction, SIGNAL(triggered(bool)), parent, SLOT(slotForwardInlineMsg())); ac->addAction(QStringLiteral("message_forward_inline"), mForwardInlineAction); setupForwardActions(ac); mRedirectAction = new QAction(i18nc("Message->Forward->", "&Redirect..."), this); ac->addAction(QStringLiteral("message_forward_redirect"), mRedirectAction); connect(mRedirectAction, SIGNAL(triggered(bool)), parent, SLOT(slotRedirectMessage())); ac->setDefaultShortcut(mRedirectAction, QKeySequence(Qt::Key_E)); mForwardActionMenu->addAction(mRedirectAction); mMailingListActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-message-new-list")), i18nc("Message->", "Mailing-&List"), this); connect(mMailingListActionMenu->menu(), &QMenu::triggered, this, &MessageActions::slotRunUrl); ac->addAction(QStringLiteral("mailing_list"), mMailingListActionMenu); mMailingListActionMenu->setEnabled(false); connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::itemChanged, this, &MessageActions::slotItemModified); connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::itemRemoved, this, &MessageActions::slotItemRemoved); mCustomTemplatesMenu = new TemplateParser::CustomTemplatesMenu(parent, ac); connect(mCustomTemplatesMenu, SIGNAL(replyTemplateSelected(QString)), parent, SLOT(slotCustomReplyToMsg(QString))); connect(mCustomTemplatesMenu, SIGNAL(replyAllTemplateSelected(QString)), parent, SLOT(slotCustomReplyAllToMsg(QString))); connect(mCustomTemplatesMenu, SIGNAL(forwardTemplateSelected(QString)), parent, SLOT(slotCustomForwardMsg(QString))); connect(KMKernel::self(), &KMKernel::customTemplatesChanged, mCustomTemplatesMenu, &TemplateParser::CustomTemplatesMenu::update); forwardMenu()->addSeparator(); forwardMenu()->addAction(mCustomTemplatesMenu->forwardActionMenu()); replyMenu()->addSeparator(); replyMenu()->addAction(mCustomTemplatesMenu->replyActionMenu()); replyMenu()->addAction(mCustomTemplatesMenu->replyAllActionMenu()); - //Don't translate it. Shown only when we set env variable KDEPIM_BALOO_DEBUG - mDebugBalooAction = new QAction(QStringLiteral("Debug Baloo..."), this); - connect(mDebugBalooAction, &QAction::triggered, this, &MessageActions::slotDebugBaloo); + //Don't translate it. Shown only when we set env variable AKONADI_SEARCH_DEBUG + mDebugAkonadiSearchAction = new QAction(QStringLiteral("Debug Akonadi Search..."), this); + connect(mDebugAkonadiSearchAction, &QAction::triggered, this, &MessageActions::slotDebugAkonadiSearch); mAddFollowupReminderAction = new QAction(i18n("Add Followup Reminder..."), this); ac->addAction(QStringLiteral("message_followup_reminder"), mAddFollowupReminderAction); connect(mAddFollowupReminderAction, &QAction::triggered, this, &MessageActions::slotAddFollowupReminder); updateActions(); } MessageActions::~MessageActions() { delete mCustomTemplatesMenu; } TemplateParser::CustomTemplatesMenu *MessageActions::customTemplatesMenu() const { return mCustomTemplatesMenu; } void MessageActions::setCurrentMessage(const Akonadi::Item &msg, const Akonadi::Item::List &items) { mCurrentItem = msg; if (!items.isEmpty()) { if (msg.isValid()) { mVisibleItems = items; } else { mVisibleItems.clear(); } } if (!msg.isValid()) { mVisibleItems.clear(); clearMailingListActions(); } updateActions(); } KActionMenu *MessageActions::replyMenu() const { return mReplyActionMenu; } QAction *MessageActions::replyListAction() const { return mReplyListAction; } QAction *MessageActions::forwardInlineAction() const { return mForwardInlineAction; } QAction *MessageActions::forwardAttachedAction() const { return mForwardAttachedAction; } QAction *MessageActions::redirectAction() const { return mRedirectAction; } KActionMenu *MessageActions::messageStatusMenu() const { return mStatusMenu; } KActionMenu *MessageActions::forwardMenu() const { return mForwardActionMenu; } QAction *MessageActions::editAction() const { return mEditAction; } QAction *MessageActions::annotateAction() const { return mAnnotateAction; } QAction *MessageActions::printAction() const { return mPrintAction; } QAction *MessageActions::printPreviewAction() const { return mPrintPreviewAction; } QAction *MessageActions::listFilterAction() const { return mListFilterAction; } KActionMenu *MessageActions::mailingListActionMenu() const { return mMailingListActionMenu; } void MessageActions::slotItemRemoved(const Akonadi::Item &item) { if (item == mCurrentItem) { mCurrentItem = Akonadi::Item(); updateActions(); } } void MessageActions::slotItemModified(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) { Q_UNUSED(partIdentifiers); if (item == mCurrentItem) { mCurrentItem = item; const int numberOfVisibleItems = mVisibleItems.count(); for (int i = 0; i < numberOfVisibleItems; ++i) { Akonadi::Item it = mVisibleItems.at(i); if (item == it) { mVisibleItems[i] = item; } } updateActions(); } } void MessageActions::updateActions() { const bool hasPayload = mCurrentItem.hasPayload(); bool itemValid = mCurrentItem.isValid(); Akonadi::Collection parent; if (itemValid) { //=> valid parent = mCurrentItem.parentCollection(); } if (parent.isValid()) { if (CommonKernel->folderIsTemplates(parent)) { itemValid = false; } } const bool multiVisible = !mVisibleItems.isEmpty() || mCurrentItem.isValid(); const bool uniqItem = (itemValid || hasPayload) && (mVisibleItems.count() <= 1); mReplyActionMenu->setEnabled(hasPayload); mReplyAction->setEnabled(hasPayload); mNoQuoteReplyAction->setEnabled(hasPayload); mReplyAuthorAction->setEnabled(hasPayload); mReplyAllAction->setEnabled(hasPayload); mReplyListAction->setEnabled(hasPayload); mNoQuoteReplyAction->setEnabled(hasPayload); mAnnotateAction->setEnabled(uniqItem); if (!mCurrentItem.hasAttribute()) { mAnnotateAction->setText(i18n("Add Note...")); } else { mAnnotateAction->setText(i18n("Edit Note...")); } mStatusMenu->setEnabled(multiVisible); if (mCurrentItem.hasPayload()) { if (mCurrentItem.loadedPayloadParts().contains("RFC822")) { updateMailingListActions(mCurrentItem); } else { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(mCurrentItem); job->fetchScope().fetchAllAttributes(); job->fetchScope().fetchFullPayload(true); job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header); job->fetchScope().fetchAttribute(); connect(job, &Akonadi::ItemFetchJob::result, this, &MessageActions::slotUpdateActionsFetchDone); } } mEditAction->setEnabled(uniqItem); } void MessageActions::slotUpdateActionsFetchDone(KJob *job) { if (job->error()) { return; } Akonadi::ItemFetchJob *fetchJob = static_cast(job); if (fetchJob->items().isEmpty()) { return; } Akonadi::Item messageItem = fetchJob->items().first(); if (messageItem == mCurrentItem) { mCurrentItem = messageItem; updateMailingListActions(messageItem); } } void MessageActions::clearMailingListActions() { mMailingListActionMenu->setEnabled(false); mListFilterAction->setEnabled(false); mListFilterAction->setText(i18n("Filter on Mailing-List...")); } void MessageActions::updateMailingListActions(const Akonadi::Item &messageItem) { if (!messageItem.hasPayload()) { return; } KMime::Message::Ptr message = messageItem.payload(); const MessageCore::MailingList mailList = MessageCore::MailingList::detect(message); if (mailList.features() == MessageCore::MailingList::None) { clearMailingListActions(); } else { // A mailing list menu with only a title is pretty boring // so make sure theres at least some content QString listId; if (mailList.features() & MessageCore::MailingList::Id) { // From a list-id in the form, "Birds of France ", // take "Birds of France" if it exists otherwise "bof.yahoo.com". listId = mailList.id(); const int start = listId.indexOf(QLatin1Char('<')); if (start > 0) { listId.truncate(start - 1); } else if (start == 0) { const int end = listId.lastIndexOf(QLatin1Char('>')); if (end < 1) { // shouldn't happen but account for it anyway listId.remove(0, 1); } else { listId = listId.mid(1, end - 1); } } } mMailingListActionMenu->menu()->clear(); qDeleteAll(mMailListActionList); mMailListActionList.clear(); if (!listId.isEmpty()) { mMailingListActionMenu->menu()->setTitle(listId); } if (mailList.features() & MessageCore::MailingList::ArchivedAt) { // IDEA: this may be something you want to copy - "Copy in submenu"? addMailingListActions(i18n("Open Message in List Archive"), mailList.archivedAtUrls()); } if (mailList.features() & MessageCore::MailingList::Post) { addMailingListActions(i18n("Post New Message"), mailList.postUrls()); } if (mailList.features() & MessageCore::MailingList::Archive) { addMailingListActions(i18n("Go to Archive"), mailList.archiveUrls()); } if (mailList.features() & MessageCore::MailingList::Help) { addMailingListActions(i18n("Request Help"), mailList.helpUrls()); } if (mailList.features() & MessageCore::MailingList::Owner) { addMailingListActions(i18nc("Contact the owner of the mailing list", "Contact Owner"), mailList.ownerUrls()); } if (mailList.features() & MessageCore::MailingList::Subscribe) { addMailingListActions(i18n("Subscribe to List"), mailList.subscribeUrls()); } if (mailList.features() & MessageCore::MailingList::Unsubscribe) { addMailingListActions(i18n("Unsubscribe from List"), mailList.unsubscribeUrls()); } mMailingListActionMenu->setEnabled(true); QByteArray name; QString value; const QString lname = MailingList::name(message, name, value); if (!lname.isEmpty()) { mListFilterAction->setEnabled(true); mListFilterAction->setText(i18n("Filter on Mailing-List %1...", lname)); } } } void MessageActions::replyCommand(MessageComposer::ReplyStrategy strategy) { if (!mCurrentItem.hasPayload()) { return; } const QString text = mMessageView ? mMessageView->copyText() : QString(); KMCommand *command = new KMReplyCommand(mParent, mCurrentItem, strategy, text); connect(command, &KMCommand::completed, this, &MessageActions::replyActionFinished); command->start(); } void MessageActions::setMessageView(KMReaderWin *msgView) { mMessageView = msgView; } void MessageActions::setupForwardActions(KActionCollection *ac) { disconnect(mForwardActionMenu, SIGNAL(triggered(bool)), nullptr, nullptr); mForwardActionMenu->removeAction(mForwardInlineAction); mForwardActionMenu->removeAction(mForwardAttachedAction); if (KMailSettings::self()->forwardingInlineByDefault()) { mForwardActionMenu->insertAction(mRedirectAction, mForwardInlineAction); mForwardActionMenu->insertAction(mRedirectAction, mForwardAttachedAction); ac->setDefaultShortcut(mForwardInlineAction, QKeySequence(Qt::Key_F)); ac->setDefaultShortcut(mForwardAttachedAction, QKeySequence(Qt::SHIFT + Qt::Key_F)); QObject::connect(mForwardActionMenu, SIGNAL(triggered(bool)), mParent, SLOT(slotForwardInlineMsg())); } else { mForwardActionMenu->insertAction(mRedirectAction, mForwardAttachedAction); mForwardActionMenu->insertAction(mRedirectAction, mForwardInlineAction); ac->setDefaultShortcut(mForwardInlineAction, QKeySequence(Qt::Key_F)); ac->setDefaultShortcut(mForwardAttachedAction, QKeySequence(Qt::SHIFT + Qt::Key_F)); QObject::connect(mForwardActionMenu, SIGNAL(triggered(bool)), mParent, SLOT(slotForwardAttachedMessage())); } } void MessageActions::setupForwardingActionsList(KXMLGUIClient *guiClient) { QList forwardActionList; guiClient->unplugActionList(QStringLiteral("forward_action_list")); if (KMailSettings::self()->forwardingInlineByDefault()) { forwardActionList.append(mForwardInlineAction); forwardActionList.append(mForwardAttachedAction); } else { forwardActionList.append(mForwardAttachedAction); forwardActionList.append(mForwardInlineAction); } forwardActionList.append(mRedirectAction); guiClient->plugActionList(QStringLiteral("forward_action_list"), forwardActionList); } void MessageActions::slotReplyToMsg() { replyCommand(MessageComposer::ReplySmart); } void MessageActions::slotReplyAuthorToMsg() { replyCommand(MessageComposer::ReplyAuthor); } void MessageActions::slotReplyListToMsg() { replyCommand(MessageComposer::ReplyList); } void MessageActions::slotReplyAllToMsg() { replyCommand(MessageComposer::ReplyAll); } void MessageActions::slotNoQuoteReplyToMsg() { if (!mCurrentItem.hasPayload()) { return; } KMCommand *command = new KMReplyCommand(mParent, mCurrentItem, MessageComposer::ReplySmart, QString(), true); command->start(); } void MessageActions::slotRunUrl(QAction *urlAction) { const QVariant q = urlAction->data(); if (q.type() == QVariant::Url) { new KRun(q.toUrl(), mParent); } } void MessageActions::slotMailingListFilter() { if (!mCurrentItem.hasPayload()) { return; } KMCommand *command = new KMMailingListFilterCommand(mParent, mCurrentItem); command->start(); } void MessageActions::printMessage(bool preview) { if (mMessageView) { bool result = false; if (MessageViewer::MessageViewerSettings::self()->printSelectedText()) { result = mMessageView->printSelectedText(preview); } if (!result) { const bool useFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont(); const QString overrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); const Akonadi::Item message = mCurrentItem; KMPrintCommand *command = new KMPrintCommand(mParent, message, mMessageView->viewer()->headerStylePlugin(), mMessageView->viewer()->displayFormatMessageOverwrite(), mMessageView->viewer()->htmlLoadExternal(), useFixedFont, overrideEncoding); command->setPrintPreview(preview); command->start(); } } } void MessageActions::slotPrintPreviewMsg() { printMessage(true); } void MessageActions::slotPrintMessage() { printMessage(false); } /** * This adds a list of actions to mMailingListActionMenu mapping the identifier item to * the url. * * e.g.: item = "Contact Owner" * "Contact Owner (email)" -> KRun( "mailto:bob@arthouseflowers.example.com" ) * "Contact Owner (web)" -> KRun( "http://arthouseflowers.example.com/contact-owner.php" ) */ void MessageActions::addMailingListActions(const QString &item, const QList &list) { for (const QUrl &url : list) { addMailingListAction(item, url); } } /** * This adds a action to mMailingListActionMenu mapping the identifier item to * the url. See addMailingListActions above. */ void MessageActions::addMailingListAction(const QString &item, const QUrl &url) { QString protocol = url.scheme().toLower(); QString prettyUrl = url.toDisplayString(); if (protocol == QLatin1String("mailto")) { protocol = i18n("email"); prettyUrl.remove(0, 7); // length( "mailto:" ) } else if (protocol.startsWith(QStringLiteral("http"))) { protocol = i18n("web"); } // item is a mailing list url description passed from the updateActions method above. QAction *act = new QAction(i18nc("%1 is a 'Contact Owner' or similar action. %2 is a protocol normally web or email though could be irc/ftp or other url variant", "%1 (%2)", item, protocol), this); mMailListActionList.append(act); const QVariant v(url); act->setData(v); KMail::Util::addQActionHelpText(act, prettyUrl); mMailingListActionMenu->addAction(act); } void MessageActions::editCurrentMessage() { KMCommand *command = nullptr; if (mCurrentItem.isValid()) { Akonadi::Collection col = mCurrentItem.parentCollection(); qCDebug(KMAIL_LOG) << " mCurrentItem.parentCollection()" << mCurrentItem.parentCollection(); // edit, unlike send again, removes the message from the folder // we only want that for templates and drafts folders if (col.isValid() && (CommonKernel->folderIsDraftOrOutbox(col) || CommonKernel->folderIsTemplates(col)) ) { command = new KMEditItemCommand(mParent, mCurrentItem, true); } else { command = new KMEditItemCommand(mParent, mCurrentItem, false); } command->start(); } else if (mCurrentItem.hasPayload()) { command = new KMEditMessageCommand(mParent, mCurrentItem.payload()); command->start(); } } void MessageActions::annotateMessage() { if (!mCurrentItem.isValid()) { return; } QPointer dialog = new PimCommon::AnnotationEditDialog(mCurrentItem, mParent); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->exec(); } void MessageActions::addWebShortcutsMenu(QMenu *menu, const QString &text) { mWebShortcutMenuManager->setSelectedText(text); mWebShortcutMenuManager->addWebShortcutsToMenu(menu); } -QAction *MessageActions::debugBalooAction() const +QAction *MessageActions::debugAkonadiSearchAction() const { - return mDebugBalooAction; + return mDebugAkonadiSearchAction; } QAction *MessageActions::addFollowupReminderAction() const { return mAddFollowupReminderAction; } -void MessageActions::slotDebugBaloo() +void MessageActions::slotDebugAkonadiSearch() { if (!mCurrentItem.isValid()) { return; } QPointer dlg = new Akonadi::Search::AkonadiSearchDebugDialog; dlg->setAkonadiId(mCurrentItem.id()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setSearchType(Akonadi::Search::AkonadiSearchDebugSearchPathComboBox::Emails); dlg->doSearch(); dlg->show(); } void MessageActions::slotAddFollowupReminder() { if (!mCurrentItem.isValid()) { return; } QPointer dlg = new MessageComposer::FollowUpReminderSelectDateDialog; if (dlg->exec()) { const QDate date = dlg->selectedDate(); CreateFollowupReminderOnExistingMessageJob *job = new CreateFollowupReminderOnExistingMessageJob(this); job->setDate(date); job->setCollection(dlg->collection()); job->setMessageItem(mCurrentItem); job->start(); } delete dlg; } diff --git a/src/messageactions.h b/src/messageactions.h index 848880dec..491c232c9 100644 --- a/src/messageactions.h +++ b/src/messageactions.h @@ -1,166 +1,166 @@ /* Copyright (c) 2007 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMAIL_MESSAGEACTIONS_H #define KMAIL_MESSAGEACTIONS_H #include #include #include class QWidget; class QAction; class KJob; class QAction; class KActionMenu; class KActionCollection; class KXMLGUIClient; class KMReaderWin; class QMenu; namespace Akonadi { class Item; } namespace TemplateParser { class CustomTemplatesMenu; } namespace KIO { class KUriFilterSearchProviderActions; } namespace KMail { /** Manages common actions that can be performed on one or more messages. */ class MessageActions : public QObject { Q_OBJECT public: explicit MessageActions(KActionCollection *ac, QWidget *parent); ~MessageActions(); void setMessageView(KMReaderWin *msgView); /** * This function adds or updates the actions of the forward action menu, taking the * preference whether to forward inline or as attachment by default into account. * This has to be called when that preference config has been changed. */ void setupForwardActions(KActionCollection *ac); /** * Sets up action list for forward menu. */ void setupForwardingActionsList(KXMLGUIClient *guiClient); void setCurrentMessage(const Akonadi::Item &item, const Akonadi::Item::List &items = Akonadi::Item::List()); KActionMenu *replyMenu() const; QAction *replyListAction() const; QAction *forwardInlineAction() const; QAction *forwardAttachedAction() const; QAction *redirectAction() const; KActionMenu *messageStatusMenu() const; KActionMenu *forwardMenu() const; QAction *editAction() const; QAction *annotateAction() const; QAction *printAction() const; QAction *printPreviewAction() const; QAction *listFilterAction() const; KActionMenu *mailingListActionMenu() const; TemplateParser::CustomTemplatesMenu *customTemplatesMenu() const; void addWebShortcutsMenu(QMenu *menu, const QString &text); - QAction *debugBalooAction() const; + QAction *debugAkonadiSearchAction() const; QAction *addFollowupReminderAction() const; Q_SIGNALS: // This signal is emitted when a reply is triggered and the // action has finished. // This is useful for the stand-alone reader, it might want to close the window in // that case. void replyActionFinished(); public Q_SLOTS: void editCurrentMessage(); void annotateMessage(); private: void updateActions(); void replyCommand(MessageComposer::ReplyStrategy strategy); void addMailingListAction(const QString &item, const QUrl &url); void addMailingListActions(const QString &item, const QList &list); void updateMailingListActions(const Akonadi::Item &messageItem); void printMessage(bool preview); void clearMailingListActions(); private Q_SLOTS: void slotItemModified(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers); void slotItemRemoved(const Akonadi::Item &item); void slotReplyToMsg(); void slotReplyAuthorToMsg(); void slotReplyListToMsg(); void slotReplyAllToMsg(); void slotNoQuoteReplyToMsg(); void slotRunUrl(QAction *urlAction); void slotPrintMessage(); void slotPrintPreviewMsg(); void slotUpdateActionsFetchDone(KJob *job); void slotMailingListFilter(); - void slotDebugBaloo(); + void slotDebugAkonadiSearch(); void slotAddFollowupReminder(); private: QList mMailListActionList; QWidget *mParent = nullptr; Akonadi::Item mCurrentItem; Akonadi::Item::List mVisibleItems; KMReaderWin *mMessageView = nullptr; KActionMenu *mReplyActionMenu = nullptr; QAction *mReplyAction = nullptr; QAction *mReplyAllAction = nullptr; QAction *mReplyAuthorAction = nullptr; QAction *mReplyListAction = nullptr; QAction *mNoQuoteReplyAction = nullptr; QAction *mForwardInlineAction = nullptr; QAction *mForwardAttachedAction = nullptr; QAction *mRedirectAction = nullptr; KActionMenu *mStatusMenu = nullptr; KActionMenu *mForwardActionMenu = nullptr; KActionMenu *mMailingListActionMenu = nullptr; QAction *mEditAction = nullptr; QAction *mAnnotateAction = nullptr; QAction *mPrintAction = nullptr; QAction *mPrintPreviewAction = nullptr; TemplateParser::CustomTemplatesMenu *mCustomTemplatesMenu = nullptr; QAction *mListFilterAction = nullptr; QAction *mAddFollowupReminderAction = nullptr; - QAction *mDebugBalooAction = nullptr; + QAction *mDebugAkonadiSearchAction = nullptr; KIO::KUriFilterSearchProviderActions *mWebShortcutMenuManager = nullptr; }; } #endif diff --git a/src/search/checkindexingmanager.cpp b/src/search/checkindexingmanager.cpp index 4fe57b5ad..e69d8d570 100644 --- a/src/search/checkindexingmanager.cpp +++ b/src/search/checkindexingmanager.cpp @@ -1,168 +1,168 @@ /* Copyright (C) 2016-2017 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "checkindexingmanager.h" #include "kmail_debug.h" #include "checkindexingjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include CheckIndexingManager::CheckIndexingManager(Akonadi::Search::PIM::IndexedItems *indexer, QObject *parent) : QObject(parent) , mIndexedItems(indexer) { mTimer = new QTimer(this); mTimer->setSingleShot(true); mTimer->setInterval(5 * 1000); //5 secondes connect(mTimer, &QTimer::timeout, this, &CheckIndexingManager::checkNextCollection); } CheckIndexingManager::~CheckIndexingManager() { callToReindexCollection(); const KSharedConfig::Ptr cfg = KSharedConfig::openConfig(QStringLiteral("kmailsearchindexingrc")); KConfigGroup grp = cfg->group(QStringLiteral("General")); grp.writeEntry(QStringLiteral("collectionsIndexed"), mCollectionsIndexed); } void CheckIndexingManager::start(QAbstractItemModel *collectionModel) { if (mIsReady) { const KSharedConfig::Ptr cfg = KSharedConfig::openConfig(QStringLiteral("kmailsearchindexingrc")); KConfigGroup grp = cfg->group(QStringLiteral("General")); const QDateTime lastDateTime = grp.readEntry(QStringLiteral("lastCheck"), QDateTime()); //Check each 7 days QDateTime today = QDateTime::currentDateTime(); if (!lastDateTime.isValid() || today > lastDateTime.addDays(7)) { mIndex = 0; mListCollection.clear(); mCollectionsIndexed = grp.readEntry(QStringLiteral("collectionsIndexed"), QList()); if (collectionModel) { initializeCollectionList(collectionModel); if (!mListCollection.isEmpty()) { qCDebug(KMAIL_LOG) << "Number of collection to check " << mListCollection.count(); mIsReady = false; mTimer->start(); } } } } } void CheckIndexingManager::createJob() { CheckIndexingJob *job = new CheckIndexingJob(mIndexedItems, this); job->setCollection(mListCollection.at(mIndex)); connect(job, &CheckIndexingJob::finished, this, &CheckIndexingManager::indexingFinished); job->start(); } void CheckIndexingManager::checkNextCollection() { if (mIndex < mListCollection.count()) { createJob(); } } void CheckIndexingManager::callToReindexCollection() { if (!mCollectionsNeedToBeReIndexed.isEmpty()) { QString service = QStringLiteral("org.freedesktop.Akonadi.Indexer"); if (Akonadi::ServerManager::hasInstanceIdentifier()) { service += QLatin1Char('.') + Akonadi::ServerManager::instanceIdentifier(); } - QDBusInterface interfaceBalooIndexer(PimCommon::MailUtil::indexerServiceName(), QStringLiteral("/"), service); - if (interfaceBalooIndexer.isValid()) { + QDBusInterface interfaceAkonadiIndexer(PimCommon::MailUtil::indexerServiceName(), QStringLiteral("/"), service); + if (interfaceAkonadiIndexer.isValid()) { qCDebug(KMAIL_LOG) << "Reindex collections :" << mCollectionsIndexed; - interfaceBalooIndexer.asyncCall(QStringLiteral("reindexCollections"), QVariant::fromValue(mCollectionsNeedToBeReIndexed)); + interfaceAkonadiIndexer.asyncCall(QStringLiteral("reindexCollections"), QVariant::fromValue(mCollectionsNeedToBeReIndexed)); } } } void CheckIndexingManager::indexingFinished(qint64 index, bool reindexCollection) { if (index != -1) { if (!mCollectionsIndexed.contains(index)) { mCollectionsIndexed.append(index); } } if (reindexCollection) { if (!mCollectionsNeedToBeReIndexed.contains(index)) { mCollectionsNeedToBeReIndexed.append(index); } if (mCollectionsNeedToBeReIndexed.count() > 30) { callToReindexCollection(); mCollectionsNeedToBeReIndexed.clear(); } } mIndex++; if (mIndex < mListCollection.count()) { mTimer->start(); } else { mIsReady = true; mIndex = 0; callToReindexCollection(); mListCollection.clear(); mCollectionsNeedToBeReIndexed.clear(); const KSharedConfig::Ptr cfg = KSharedConfig::openConfig(QStringLiteral("kmailsearchindexingrc")); KConfigGroup grp = cfg->group(QStringLiteral("General")); grp.writeEntry(QStringLiteral("lastCheck"), QDateTime::currentDateTime()); grp.deleteEntry(QStringLiteral("collectionsIndexed")); grp.sync(); } } void CheckIndexingManager::initializeCollectionList(QAbstractItemModel *model, const QModelIndex &parentIndex) { const int rowCount = model->rowCount(parentIndex); for (int row = 0; row < rowCount; ++row) { const QModelIndex index = model->index(row, 0, parentIndex); const Akonadi::Collection collection = model->data( index, Akonadi::EntityTreeModel::CollectionRole).value(); if (!collection.isValid() || MailCommon::Util::isVirtualCollection(collection)) { continue; } if (collection.hasAttribute()) { continue; } if (PimCommon::Util::isImapResource(collection.resource()) && !collection.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { continue; } if (!mCollectionsIndexed.contains(collection.id())) { mListCollection.append(collection); } if (model->rowCount(index) > 0) { initializeCollectionList(model, index); } } }