diff --git a/agents/mailfilteragent/filterlogdialog.cpp b/agents/mailfilteragent/filterlogdialog.cpp index 50c9f5806..015170530 100644 --- a/agents/mailfilteragent/filterlogdialog.cpp +++ b/agents/mailfilteragent/filterlogdialog.cpp @@ -1,401 +1,400 @@ /* Copyright (c) 2003 Andreas Gungl Copyright (C) 2012-2018 Laurent Montel 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "filterlogdialog.h" #include #include "kpimtextedit/plaintexteditorwidget.h" #include "kpimtextedit/plaintexteditor.h" #include "mailfilterpurposemenuwidget.h" #include "mailfilteragent_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; FilterLogDialog::FilterLogDialog(QWidget *parent) : QDialog(parent) , mIsInitialized(false) { setWindowTitle(i18n("Filter Log Viewer")); QVBoxLayout *mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); mUser1Button = new QPushButton(this); buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole); mUser2Button = new QPushButton(this); buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole); connect(buttonBox, &QDialogButtonBox::rejected, this, &FilterLogDialog::reject); setWindowIcon(QIcon::fromTheme(QStringLiteral("view-filter"))); setModal(false); - buttonBox->button(QDialogButtonBox::Close)->setDefault(true); KGuiItem::assign(mUser1Button, KStandardGuiItem::clear()); KGuiItem::assign(mUser2Button, KStandardGuiItem::saveAs()); QFrame *page = new QFrame(this); QVBoxLayout *pageVBoxLayout = new QVBoxLayout; page->setLayout(pageVBoxLayout); pageVBoxLayout->setMargin(0); mainLayout->addWidget(page); mTextEdit = new KPIMTextEdit::PlainTextEditorWidget(new FilterLogTextEdit(this), page); pageVBoxLayout->addWidget(mTextEdit); mTextEdit->setReadOnly(true); mTextEdit->editor()->setWordWrapMode(QTextOption::NoWrap); const QStringList logEntries = FilterLog::instance()->logEntries(); QStringList::ConstIterator end(logEntries.constEnd()); for (QStringList::ConstIterator it = logEntries.constBegin(); it != end; ++it) { mTextEdit->editor()->appendHtml(*it); } MailfilterPurposeMenuWidget *purposeMenu = new MailfilterPurposeMenuWidget(this, this); if (purposeMenu->menu()) { QPushButton *mShareButton = new QPushButton(i18n("Share..."), this); mShareButton->setMenu(purposeMenu->menu()); mShareButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); purposeMenu->setEditorWidget(mTextEdit->editor()); buttonBox->addButton(mShareButton, QDialogButtonBox::ActionRole); } else { delete purposeMenu; } mLogActiveBox = new QCheckBox(i18n("&Log filter activities"), page); pageVBoxLayout->addWidget(mLogActiveBox); mLogActiveBox->setChecked(FilterLog::instance()->isLogging()); connect(mLogActiveBox, &QCheckBox::clicked, this, &FilterLogDialog::slotSwitchLogState); mLogActiveBox->setWhatsThis( i18n("You can turn logging of filter activities on and off here. " "Of course, log data is collected and shown only when logging " "is turned on. ")); mLogDetailsBox = new QGroupBox(i18n("Logging Details"), page); pageVBoxLayout->addWidget(mLogDetailsBox); QVBoxLayout *layout = new QVBoxLayout; mLogDetailsBox->setLayout(layout); mLogDetailsBox->setEnabled(mLogActiveBox->isChecked()); connect(mLogActiveBox, &QCheckBox::toggled, mLogDetailsBox, &QGroupBox::setEnabled); mLogPatternDescBox = new QCheckBox(i18n("Log pattern description")); layout->addWidget(mLogPatternDescBox); mLogPatternDescBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)); connect(mLogPatternDescBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail); // TODO //QWhatsThis::add( mLogPatternDescBox, // i18n( "" ) ); mLogRuleEvaluationBox = new QCheckBox(i18n("Log filter &rule evaluation")); layout->addWidget(mLogRuleEvaluationBox); mLogRuleEvaluationBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)); connect(mLogRuleEvaluationBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail); mLogRuleEvaluationBox->setWhatsThis( i18n("You can control the feedback in the log concerning the " "evaluation of the filter rules of applied filters: " "having this option checked will give detailed feedback " "for each single filter rule; alternatively, only " "feedback about the result of the evaluation of all rules " "of a single filter will be given.")); mLogPatternResultBox = new QCheckBox(i18n("Log filter pattern evaluation")); layout->addWidget(mLogPatternResultBox); mLogPatternResultBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)); connect(mLogPatternResultBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail); // TODO //QWhatsThis::add( mLogPatternResultBox, // i18n( "" ) ); mLogFilterActionBox = new QCheckBox(i18n("Log filter actions")); layout->addWidget(mLogFilterActionBox); mLogFilterActionBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)); connect(mLogFilterActionBox, &QCheckBox::clicked, this, &FilterLogDialog::slotChangeLogDetail); // TODO //QWhatsThis::add( mLogFilterActionBox, // i18n( "" ) ); QWidget *hbox = new QWidget(page); QHBoxLayout *hboxHBoxLayout = new QHBoxLayout; hbox->setLayout(hboxHBoxLayout); hboxHBoxLayout->setMargin(0); pageVBoxLayout->addWidget(hbox); QLabel *logSizeLab = new QLabel(i18n("Log size limit:"), hbox); hboxHBoxLayout->addWidget(logSizeLab); mLogMemLimitSpin = new QSpinBox(hbox); hboxHBoxLayout->addWidget(mLogMemLimitSpin); mLogMemLimitSpin->setMinimum(1); mLogMemLimitSpin->setMaximum(1024 * 256); // 256 MB // value in the QSpinBox is in KB while it's in Byte in the FilterLog mLogMemLimitSpin->setValue(FilterLog::instance()->maxLogSize() / 1024); mLogMemLimitSpin->setSuffix(i18n(" KB")); mLogMemLimitSpin->setSpecialValueText( i18nc("@label:spinbox Set the size of the logfile to unlimited.", "unlimited")); connect(mLogMemLimitSpin, QOverload::of(&QSpinBox::valueChanged), this, &FilterLogDialog::slotChangeLogMemLimit); mLogMemLimitSpin->setWhatsThis( i18n("Collecting log data uses memory to temporarily store the " "log data; here you can limit the maximum amount of memory " "to be used: if the size of the collected log data exceeds " "this limit then the oldest data will be discarded until " "the limit is no longer exceeded. ")); connect(FilterLog::instance(), &FilterLog::logEntryAdded, this, &FilterLogDialog::slotLogEntryAdded); connect(FilterLog::instance(), &FilterLog::logShrinked, this, &FilterLogDialog::slotLogShrinked); connect(FilterLog::instance(), &FilterLog::logStateChanged, this, &FilterLogDialog::slotLogStateChanged); mainLayout->addWidget(buttonBox); connect(mUser1Button, &QPushButton::clicked, this, &FilterLogDialog::slotUser1); connect(mUser2Button, &QPushButton::clicked, this, &FilterLogDialog::slotUser2); connect(mTextEdit->editor(), &KPIMTextEdit::PlainTextEditor::textChanged, this, &FilterLogDialog::slotTextChanged); slotTextChanged(); readConfig(); mIsInitialized = true; } void FilterLogDialog::slotTextChanged() { const bool hasText = !mTextEdit->isEmpty(); mUser2Button->setEnabled(hasText); mUser1Button->setEnabled(hasText); } void FilterLogDialog::readConfig() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group(config, "FilterLog"); const bool isEnabled = group.readEntry("Enabled", false); const bool isLogPatternDescription = group.readEntry("LogPatternDescription", false); const bool isLogRuleResult = group.readEntry("LogRuleResult", false); const bool isLogPatternResult = group.readEntry("LogPatternResult", false); const bool isLogAppliedAction = group.readEntry("LogAppliedAction", false); const int maxLogSize = group.readEntry("maxLogSize", -1); if (isEnabled != FilterLog::instance()->isLogging()) { FilterLog::instance()->setLogging(isEnabled); } if (isLogPatternDescription != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternDescription, isLogPatternDescription); } if (isLogRuleResult != FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::RuleResult, isLogRuleResult); } if (isLogPatternResult != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternResult, isLogPatternResult); } if (isLogAppliedAction != FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::AppliedAction, isLogAppliedAction); } if (FilterLog::instance()->maxLogSize() != maxLogSize) { FilterLog::instance()->setMaxLogSize(maxLogSize); } KConfigGroup geometryGroup(config, "Geometry"); const QSize size = geometryGroup.readEntry("filterLogSize", QSize(600, 400)); if (size.isValid()) { resize(size); } else { adjustSize(); } } FilterLogDialog::~FilterLogDialog() { disconnect(mTextEdit->editor(), &KPIMTextEdit::PlainTextEditor::textChanged, this, &FilterLogDialog::slotTextChanged); KConfigGroup myGroup(KSharedConfig::openConfig(), "Geometry"); myGroup.writeEntry("filterLogSize", size()); myGroup.sync(); } void FilterLogDialog::writeConfig() { if (!mIsInitialized) { return; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group(config, "FilterLog"); group.writeEntry("Enabled", FilterLog::instance()->isLogging()); group.writeEntry("LogPatternDescription", FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)); group.writeEntry("LogRuleResult", FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)); group.writeEntry("LogPatternResult", FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)); group.writeEntry("LogAppliedAction", FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)); group.writeEntry("maxLogSize", static_cast(FilterLog::instance()->maxLogSize())); group.sync(); } void FilterLogDialog::slotLogEntryAdded(const QString &logEntry) { mTextEdit->editor()->appendHtml(logEntry); } void FilterLogDialog::slotLogShrinked() { // limit the size of the shown log lines as soon as // the log has reached it's memory limit if (mTextEdit->editor()->document()->maximumBlockCount() <= 0) { mTextEdit->editor()->document()->setMaximumBlockCount(mTextEdit->editor()->document()->blockCount()); } } void FilterLogDialog::slotLogStateChanged() { mLogActiveBox->setChecked(FilterLog::instance()->isLogging()); mLogPatternDescBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)); mLogRuleEvaluationBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)); mLogPatternResultBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)); mLogFilterActionBox->setChecked( FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)); // value in the QSpinBox is in KB while it's in Byte in the FilterLog int newLogSize = FilterLog::instance()->maxLogSize() / 1024; if (mLogMemLimitSpin->value() != newLogSize) { if (newLogSize <= 0) { mLogMemLimitSpin->setValue(1); } else { mLogMemLimitSpin->setValue(newLogSize); } } writeConfig(); } void FilterLogDialog::slotChangeLogDetail() { if (mLogPatternDescBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternDescription)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternDescription, mLogPatternDescBox->isChecked()); } if (mLogRuleEvaluationBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::RuleResult)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::RuleResult, mLogRuleEvaluationBox->isChecked()); } if (mLogPatternResultBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::PatternResult)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::PatternResult, mLogPatternResultBox->isChecked()); } if (mLogFilterActionBox->isChecked() != FilterLog::instance()->isContentTypeEnabled(FilterLog::AppliedAction)) { FilterLog::instance()->setContentTypeEnabled(FilterLog::AppliedAction, mLogFilterActionBox->isChecked()); } } void FilterLogDialog::slotSwitchLogState() { FilterLog::instance()->setLogging(mLogActiveBox->isChecked()); } void FilterLogDialog::slotChangeLogMemLimit(int value) { mTextEdit->editor()->document()->setMaximumBlockCount(0); //Reset value if (value == 1) { //unilimited FilterLog::instance()->setMaxLogSize(-1); } else { FilterLog::instance()->setMaxLogSize(value * 1024); } } void FilterLogDialog::slotUser1() { FilterLog::instance()->clear(); mTextEdit->editor()->clear(); } void FilterLogDialog::slotUser2() { QPointer fdlg(new QFileDialog(this)); fdlg->setAcceptMode(QFileDialog::AcceptSave); fdlg->setFileMode(QFileDialog::AnyFile); fdlg->selectFile(QStringLiteral("kmail-filter.html")); if (fdlg->exec() == QDialog::Accepted) { const QStringList fileName = fdlg->selectedFiles(); if (!fileName.isEmpty() && !FilterLog::instance()->saveToFile(fileName.at(0))) { KMessageBox::error(this, i18n("Could not write the file %1:\n" "\"%2\" is the detailed error description.", fileName.at(0), QString::fromLocal8Bit(strerror(errno))), i18n("KMail Error")); } } delete fdlg; } FilterLogTextEdit::FilterLogTextEdit(QWidget *parent) : KPIMTextEdit::PlainTextEditor(parent) { } void FilterLogTextEdit::addExtraMenuEntry(QMenu *menu, QPoint pos) { Q_UNUSED(pos); if (!document()->isEmpty()) { QAction *sep = new QAction(menu); sep->setSeparator(true); menu->addAction(sep); QAction *clearAllAction = KStandardAction::clear(this, &FilterLogTextEdit::clear, menu); menu->addAction(clearAllAction); } } diff --git a/agents/mailfilteragent/mailfilterpurposemenuwidget.cpp b/agents/mailfilteragent/mailfilterpurposemenuwidget.cpp index 2d7529a2e..fc85dc2e1 100644 --- a/agents/mailfilteragent/mailfilterpurposemenuwidget.cpp +++ b/agents/mailfilteragent/mailfilterpurposemenuwidget.cpp @@ -1,43 +1,42 @@ /* Copyright (C) 2018 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mailfilterpurposemenuwidget.h" #include MailfilterPurposeMenuWidget::MailfilterPurposeMenuWidget(QWidget *parentWidget, QObject *parent) : PimCommon::PurposeMenuWidget(parentWidget, parent) { - } MailfilterPurposeMenuWidget::~MailfilterPurposeMenuWidget() { } QByteArray MailfilterPurposeMenuWidget::text() { if (mEditor) { return mEditor->toPlainText().toUtf8(); } return {}; } void MailfilterPurposeMenuWidget::setEditorWidget(KPIMTextEdit::PlainTextEditor *editor) { mEditor = editor; } diff --git a/agents/unifiedmailboxagent/autotests/unifiedmailboxmanagertest.cpp b/agents/unifiedmailboxagent/autotests/unifiedmailboxmanagertest.cpp index e4e531627..df12e5106 100644 --- a/agents/unifiedmailboxagent/autotests/unifiedmailboxmanagertest.cpp +++ b/agents/unifiedmailboxagent/autotests/unifiedmailboxmanagertest.cpp @@ -1,666 +1,685 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "../unifiedmailboxmanager.h" #include "../unifiedmailbox.h" #include "../common.h" #include "../utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono; using namespace std::chrono_literals; namespace { - #define AKVERIFY_RET(statement, ret) \ -do {\ - if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__))\ - return ret;\ -} while (false) + do { \ + if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__)) { \ + return ret;} \ + } while (false) #define AKCOMPARE_RET(actual, expected, ret) \ -do {\ - if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ - return ret;\ -} while (false) - - + do { \ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) { \ + return ret;} \ + } while (false) stdx::optional collectionForId(qint64 id) { auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base); fetch->fetchScope().fetchAttribute(); AKVERIFY_RET(fetch->exec(), stdx::nullopt); const auto cols = fetch->collections(); AKCOMPARE_RET(cols.count(), 1, stdx::nullopt); AKVERIFY_RET(cols.first().isValid(), stdx::nullopt); return cols.first(); } stdx::optional collectionForRid(const QString &rid) { auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive); fetch->fetchScope().fetchAttribute(); fetch->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); AKVERIFY_RET(fetch->exec(), stdx::nullopt); const auto cols = fetch->collections(); auto colIt = std::find_if(cols.cbegin(), cols.cend(), [&rid](const Akonadi::Collection &col) { - return col.remoteId() == rid; - }); + return col.remoteId() == rid; + }); AKVERIFY_RET(colIt != cols.cend(), stdx::nullopt); return *colIt; } std::unique_ptr createUnifiedMailbox(const QString &id, const QString &name, const QStringList &sourceRids) { auto mailbox = std::make_unique(); mailbox->setId(id); mailbox->setName(name); mailbox->setIcon(QStringLiteral("dummy-icon")); for (const auto &srcRid : sourceRids) { const auto srcCol = collectionForRid(srcRid); AKVERIFY_RET(srcCol, {}); mailbox->addSourceCollection(srcCol->id()); } return mailbox; } class EntityDeleter { public: ~EntityDeleter() { while (!cols.isEmpty()) { if (!(new Akonadi::CollectionDeleteJob(cols.takeFirst()))->exec()) { QFAIL("Failed to cleanup collection!"); } } while (!items.isEmpty()) { if (!(new Akonadi::ItemDeleteJob(items.takeFirst()))->exec()) { QFAIL("Failed to cleanup Item"); } } } EntityDeleter &operator<<(const Akonadi::Collection &col) { cols.push_back(col); return *this; } EntityDeleter &operator<<(const Akonadi::Item &item) { items.push_back(item); return *this; } + private: Akonadi::Collection::List cols; Akonadi::Item::List items; }; stdx::optional createCollection(const QString &name, const Akonadi::Collection &parent, EntityDeleter &deleter) { Akonadi::Collection col; col.setName(name); col.setParentCollection(parent); col.setVirtual(true); auto createCol = new Akonadi::CollectionCreateJob(col); AKVERIFY_RET(createCol->exec(), stdx::nullopt); col = createCol->collection(); if (col.isValid()) { deleter << col; return col; } return stdx::nullopt; } - - } // namespace - class UnifiedMailboxManagerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { AkonadiTest::checkTestIsIsolated(); } void testCreateDefaultBoxes() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); const auto boxesGroup = kcfg->group("UnifiedMailboxes"); UnifiedMailboxManager manager(kcfg); // Make sure the config is empty QVERIFY(boxesGroup.groupList().empty()); // Call loadBoxes and wait for it to finish bool loadingDone = false; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Check all the three boxes were created bool success; const auto verifyBox = [&manager, &success](const QString &id, int numSources) { - success = false; - auto boxIt = std::find_if(manager.begin(), manager.end(), - [&id](const UnifiedMailboxManager::Entry &e) { - return e.second->id() == id; - }); - QVERIFY(boxIt != manager.end()); - const auto &box = boxIt->second; - const auto sourceCollections = box->sourceCollections(); - QCOMPARE(sourceCollections.size(), numSources); - for (auto source : sourceCollections) { - auto col = collectionForId(source); - QVERIFY(col); - QVERIFY(col->hasAttribute()); - QCOMPARE(col->attribute()->collectionType(), id.toLatin1()); - } - success = true; - }; + success = false; + auto boxIt = std::find_if(manager.begin(), manager.end(), + [&id](const UnifiedMailboxManager::Entry &e) { + return e.second->id() == id; + }); + QVERIFY(boxIt != manager.end()); + const auto &box = boxIt->second; + const auto sourceCollections = box->sourceCollections(); + QCOMPARE(sourceCollections.size(), numSources); + for (auto source : sourceCollections) { + auto col = collectionForId(source); + QVERIFY(col); + QVERIFY(col->hasAttribute()); + QCOMPARE(col->attribute()->collectionType(), id.toLatin1()); + } + success = true; + }; verifyBox(Common::InboxBoxId, 2); QVERIFY(success); verifyBox(Common::SentBoxId, 2); QVERIFY(success); verifyBox(Common::DraftsBoxId, 1); QVERIFY(success); // Check boxes were written to config - we don't check the contents of // the group, testing UnifiedMailbox serialization is done in other tests QVERIFY(boxesGroup.groupList().size() == 3); QVERIFY(boxesGroup.hasGroup(Common::InboxBoxId)); QVERIFY(boxesGroup.hasGroup(Common::SentBoxId)); QVERIFY(boxesGroup.hasGroup(Common::DraftsBoxId)); } void testAddingNewMailbox() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); const auto boxesGroup = kcfg->group("UnifiedMailboxes"); UnifiedMailboxManager manager(kcfg); Akonadi::ChangeRecorder &recorder = manager.changeRecorder(); // Nothing should be monitored as of now QVERIFY(recorder.collectionsMonitored().isEmpty()); // Create a new unified mailbox and passit to the manager auto mailbox = createUnifiedMailbox(QStringLiteral("Test1"), QStringLiteral("Test 1"), { QStringLiteral("res1_inbox") }); QVERIFY(mailbox); const auto sourceCol = mailbox->sourceCollections().toList().first(); manager.insertBox(std::move(mailbox)); // Now manager should have one unified mailbox and monitor all of its // source collections QCOMPARE(std::distance(manager.begin(), manager.end()), 1l); QCOMPARE(recorder.collectionsMonitored().size(), 1); QCOMPARE(recorder.collectionsMonitored().at(0).id(), sourceCol); QVERIFY(manager.unifiedMailboxForSource(sourceCol) != nullptr); // But nothing should bne written in the config yet QVERIFY(!boxesGroup.groupList().contains(QLatin1String("Test1"))); // Now write to the config file and check it's actually there - we don't test // the contents of the group, UnifiedMailbox serialization has its own test manager.saveBoxes(); QVERIFY(boxesGroup.hasGroup(QLatin1String("Test1"))); } void testRemoveMailbox() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); auto boxesGroup = kcfg->group("UnifiedMailboxes"); auto mailbox = createUnifiedMailbox(QStringLiteral("Test1"), QStringLiteral("Test 1"), { QStringLiteral("res1_foo"), QStringLiteral("res2_foo") }); QVERIFY(mailbox); auto group = boxesGroup.group(mailbox->id()); mailbox->save(group); UnifiedMailboxManager manager(kcfg); Akonadi::ChangeRecorder &recorder = manager.changeRecorder(); // Nothing should be monitored right now QVERIFY(recorder.collectionsMonitored().isEmpty()); // Load the config bool loadingDone = false; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now the box should be loaded and its source collections monitored QCOMPARE(std::distance(manager.begin(), manager.end()), 1l); QCOMPARE(recorder.collectionsMonitored().count(), 2); const auto srcCols = mailbox->sourceCollections().toList(); QCOMPARE(srcCols.count(), 2); QVERIFY(recorder.collectionsMonitored().contains(Akonadi::Collection(srcCols[0]))); QVERIFY(recorder.collectionsMonitored().contains(Akonadi::Collection(srcCols[1]))); // Now remove the box manager.removeBox(mailbox->id()); // Manager should have no boxes and no source collections should be monitored QVERIFY(manager.begin() == manager.end()); QVERIFY(recorder.collectionsMonitored().isEmpty()); // But the box still exists in the config QVERIFY(boxesGroup.hasGroup(mailbox->id())); // Save the new state manager.saveBoxes(); // And now it should be gone from the config file as well QVERIFY(!boxesGroup.hasGroup(mailbox->id())); } void testDiscoverBoxCollections() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); auto boxesGroup = kcfg->group("UnifiedMailboxes"); UnifiedMailboxManager manager(kcfg); EntityDeleter deleter; const auto inbox = createUnifiedMailbox(Common::InboxBoxId, QStringLiteral("Inbox"), - { QStringLiteral("res1_inbox"), QStringLiteral("res2_inbox") }); + { QStringLiteral("res1_inbox"), QStringLiteral("res2_inbox") }); auto boxGroup = boxesGroup.group(inbox->id()); inbox->save(boxGroup); const auto sentBox = createUnifiedMailbox(Common::SentBoxId, QStringLiteral("Sent"), - { QStringLiteral("res1_sent"), QStringLiteral("res2_sent") }); + { QStringLiteral("res1_sent"), QStringLiteral("res2_sent") }); boxGroup = boxesGroup.group(sentBox->id()); sentBox->save(boxGroup); const auto parentCol = collectionForRid(Common::AgentIdentifier); QVERIFY(parentCol); const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol.value(), deleter); QVERIFY(inboxBoxCol); const auto sentBoxCol = createCollection(Common::SentBoxId, parentCol.value(), deleter); QVERIFY(sentBoxCol); // Load from config bool loadingDone = false; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now the boxes should be loaded and we should be able to access them // by IDs of collections that represent them. The collections should also // be set for each box. auto box = manager.unifiedMailboxFromCollection(inboxBoxCol.value()); QVERIFY(box != nullptr); QCOMPARE(box->collectionId().value(), inboxBoxCol->id()); box = manager.unifiedMailboxFromCollection(sentBoxCol.value()); QVERIFY(box != nullptr); QCOMPARE(box->collectionId().value(), sentBoxCol->id()); } void testItemAddedToSourceCollection() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); UnifiedMailboxManager manager(kcfg); EntityDeleter deleter; const auto parentCol = collectionForRid(Common::AgentIdentifier); QVERIFY(parentCol); const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol.value(), deleter); QVERIFY(inboxBoxCol); // Load boxes - config is empty so this will create the default Boxes and // assign the Inboxes from Knuts to it bool loadingDone = true; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now discover collections for the created boxes loadingDone = false; - manager.discoverBoxCollections([&loadingDone]() { loadingDone = true; }); + manager.discoverBoxCollections([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Get one of the source collections for Inbox const auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox")); QVERIFY(inboxSourceCol); // Setup up a monitor to to be notified when an item gets linked into // the unified mailbox collection Akonadi::Monitor monitor; monitor.setCollectionMonitored(inboxBoxCol.value()); QSignalSpy itemLinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsLinked); QVERIFY(QSignalSpy(&monitor, &Akonadi::Monitor::monitorReady).wait()); // Add a new Item into the source collection Akonadi::Item item; item.setMimeType(QStringLiteral("application/octet-stream")); item.setParentCollection(inboxSourceCol.value()); item.setPayload(QByteArray{"Hello world!"}); auto createItem = new Akonadi::ItemCreateJob(item, inboxSourceCol.value(), this); AKVERIFYEXEC(createItem); item = createItem->item(); deleter << item; // Then wait for ItemLinked notification as the Manager has linked the new Item // to the dest collection QTRY_COMPARE(itemLinkedSignalSpy.size(), 1); const auto linkedItems = itemLinkedSignalSpy.at(0).at(0).value(); QCOMPARE(linkedItems.size(), 1); QCOMPARE(linkedItems.at(0), item); const auto linkedCol = itemLinkedSignalSpy.at(0).at(1).value(); QCOMPARE(linkedCol, inboxBoxCol.value()); } void testItemMovedFromSourceCollection() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); UnifiedMailboxManager manager(kcfg); EntityDeleter deleter; const auto parentCol = collectionForRid(Common::AgentIdentifier); QVERIFY(parentCol); const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol.value(), deleter); QVERIFY(inboxBoxCol); // Load boxes - config is empty so this will create the default Boxes and // assign the Inboxes from Knuts to it bool loadingDone = true; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now discover collections for the created boxes loadingDone = false; - manager.discoverBoxCollections([&loadingDone]() { loadingDone = true; }); + manager.discoverBoxCollections([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Get one of the source collections for Inbox const auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox")); QVERIFY(inboxSourceCol); // Setup up a monitor to to be notified when an item gets linked into // the unified mailbox collection Akonadi::Monitor monitor; monitor.setCollectionMonitored(inboxBoxCol.value()); QSignalSpy itemLinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsLinked); QSignalSpy itemUnlinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsUnlinked); QVERIFY(QSignalSpy(&monitor, &Akonadi::Monitor::monitorReady).wait()); // Add a new Item into the source collection Akonadi::Item item; item.setMimeType(QStringLiteral("application/octet-stream")); item.setParentCollection(inboxSourceCol.value()); item.setPayload(QByteArray{"Hello world!"}); auto createItem = new Akonadi::ItemCreateJob(item, inboxSourceCol.value(), this); AKVERIFYEXEC(createItem); item = createItem->item(); deleter << item; // Waity for the item to be linked QTRY_COMPARE(itemLinkedSignalSpy.size(), 1); const auto destinationCol = collectionForRid(QStringLiteral("res1_foo")); QVERIFY(destinationCol); // Now move the Item to an unmonitored collection auto move = new Akonadi::ItemMoveJob(item, destinationCol.value(), this); AKVERIFYEXEC(move); QTRY_COMPARE(itemUnlinkedSignalSpy.size(), 1); const auto unlinkedItems = itemUnlinkedSignalSpy.at(0).at(0).value(); QCOMPARE(unlinkedItems.size(), 1); QCOMPARE(unlinkedItems.first(), item); const auto unlinkedCol = itemUnlinkedSignalSpy.at(0).at(1).value(); QCOMPARE(unlinkedCol, inboxBoxCol.value()); } void testItemMovedBetweenSourceCollections() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); UnifiedMailboxManager manager(kcfg); EntityDeleter deleter; const auto parentCol = collectionForRid(Common::AgentIdentifier); QVERIFY(parentCol); const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol.value(), deleter); QVERIFY(inboxBoxCol); const auto draftsBoxCol = createCollection(Common::DraftsBoxId, parentCol.value(), deleter); QVERIFY(draftsBoxCol); // Load boxes - config is empty so this will create the default Boxes and // assign the Inboxes from Knuts to it bool loadingDone = true; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now discover collections for the created boxes loadingDone = false; - manager.discoverBoxCollections([&loadingDone]() { loadingDone = true; }); + manager.discoverBoxCollections([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Get one of the source collections for Inbox and Drafts const auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox")); QVERIFY(inboxSourceCol); const auto draftsSourceCol = collectionForRid(QStringLiteral("res1_drafts")); QVERIFY(draftsSourceCol); - // Setup up a monitor to to be notified when an item gets linked into // the unified mailbox collection Akonadi::Monitor monitor; monitor.setCollectionMonitored(inboxBoxCol.value()); monitor.setCollectionMonitored(draftsBoxCol.value()); QSignalSpy itemLinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsLinked); QSignalSpy itemUnlinkedSignalSpy(&monitor, &Akonadi::Monitor::itemsUnlinked); QVERIFY(QSignalSpy(&monitor, &Akonadi::Monitor::monitorReady).wait()); // Add a new Item into the source Inbox collection Akonadi::Item item; item.setMimeType(QStringLiteral("application/octet-stream")); item.setParentCollection(inboxSourceCol.value()); item.setPayload(QByteArray{"Hello world!"}); auto createItem = new Akonadi::ItemCreateJob(item, inboxSourceCol.value(), this); AKVERIFYEXEC(createItem); item = createItem->item(); deleter << item; // Waity for the item to be linked QTRY_COMPARE(itemLinkedSignalSpy.size(), 1); itemLinkedSignalSpy.clear(); // Now move the Item to another Unified mailbox's source collection auto move = new Akonadi::ItemMoveJob(item, draftsSourceCol.value(), this); AKVERIFYEXEC(move); QTRY_COMPARE(itemUnlinkedSignalSpy.size(), 1); const auto unlinkedItems = itemUnlinkedSignalSpy.at(0).at(0).value(); QCOMPARE(unlinkedItems.size(), 1); QCOMPARE(unlinkedItems.first(), item); const auto unlinkedCol = itemUnlinkedSignalSpy.at(0).at(1).value(); QCOMPARE(unlinkedCol, inboxBoxCol.value()); QTRY_COMPARE(itemLinkedSignalSpy.size(), 1); const auto linkedItems = itemLinkedSignalSpy.at(0).at(0).value(); QCOMPARE(linkedItems.size(), 1); QCOMPARE(linkedItems.first(), item); const auto linkedCol = itemLinkedSignalSpy.at(0).at(1).value(); QCOMPARE(linkedCol, draftsBoxCol.value()); } void testSourceCollectionRemoved() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); UnifiedMailboxManager manager(kcfg); auto &changeRecorder = manager.changeRecorder(); QSignalSpy crRemovedSpy(&changeRecorder, &Akonadi::Monitor::collectionRemoved); EntityDeleter deleter; const auto parentCol = collectionForRid(Common::AgentIdentifier); QVERIFY(parentCol); const auto inboxBoxCol = createCollection(Common::InboxBoxId, parentCol.value(), deleter); QVERIFY(inboxBoxCol); // Load boxes - config is empty so this will create the default Boxes and // assign the Inboxes from Knuts to it bool loadingDone = true; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now discover collections for the created boxes loadingDone = false; - manager.discoverBoxCollections([&loadingDone]() { loadingDone = true; }); + manager.discoverBoxCollections([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); auto inboxSourceCol = collectionForRid(QStringLiteral("res1_inbox")); QVERIFY(inboxSourceCol); auto delJob = new Akonadi::CollectionDeleteJob(inboxSourceCol.value(), this); AKVERIFYEXEC(delJob); // Wait for the change recorder to be notified QVERIFY(crRemovedSpy.wait()); crRemovedSpy.clear(); // and then wait a little bit more to give the Manager time to process the event QTest::qWait(0); auto inboxBox = manager.unifiedMailboxFromCollection(inboxBoxCol.value()); QVERIFY(inboxBox); QVERIFY(!inboxBox->sourceCollections().contains(inboxSourceCol->id())); QVERIFY(!changeRecorder.collectionsMonitored().contains(inboxSourceCol.value())); QVERIFY(!manager.unifiedMailboxForSource(inboxSourceCol->id())); // Lets removed the other source collection now, that should remove the unified box completely inboxSourceCol = collectionForRid(QStringLiteral("res2_inbox")); QVERIFY(inboxSourceCol); delJob = new Akonadi::CollectionDeleteJob(inboxSourceCol.value(), this); AKVERIFYEXEC(delJob); // Wait for the change recorder once again QVERIFY(crRemovedSpy.wait()); QTest::qWait(0); QVERIFY(!manager.unifiedMailboxFromCollection(inboxBoxCol.value())); QVERIFY(!changeRecorder.collectionsMonitored().contains(inboxSourceCol.value())); QVERIFY(!manager.unifiedMailboxForSource(inboxSourceCol->id())); } void testSpecialSourceCollectionCreated() { // TODO: this does not work yet: we only monitor collections that we are // interested in, we don't monitor other collections } void testSpecialSourceCollectionDemoted() { // Setup auto kcfg = KSharedConfig::openConfig(QString::fromUtf8(QTest::currentTestFunction())); UnifiedMailboxManager manager(kcfg); auto &changeRecorder = manager.changeRecorder(); QSignalSpy crChangedSpy(&changeRecorder, qOverload &>(&Akonadi::Monitor::collectionChanged)); EntityDeleter deleter; const auto parentCol = collectionForRid(Common::AgentIdentifier); QVERIFY(parentCol); const auto sentBoxCol = createCollection(Common::SentBoxId, parentCol.value(), deleter); QVERIFY(sentBoxCol); // Load boxes - config is empty so this will create the default Boxes and // assign the Inboxes from Knuts to it bool loadingDone = true; - manager.loadBoxes([&loadingDone]() { loadingDone = true; }); + manager.loadBoxes([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); // Now discover collections for the created boxes loadingDone = false; - manager.discoverBoxCollections([&loadingDone]() { loadingDone = true; }); + manager.discoverBoxCollections([&loadingDone]() { + loadingDone = true; + }); QTRY_VERIFY_WITH_TIMEOUT(loadingDone, milliseconds(10s).count()); auto sentSourceCol = collectionForRid(QStringLiteral("res1_sent")); QVERIFY(sentSourceCol); sentSourceCol->removeAttribute(); auto modify = new Akonadi::CollectionModifyJob(sentSourceCol.value(), this); AKVERIFYEXEC(modify); // Wait for the change recorder to be notified QVERIFY(crChangedSpy.wait()); crChangedSpy.clear(); // and then wait a little bit more to give the Manager time to process the event QTest::qWait(0); auto sourceBox = manager.unifiedMailboxFromCollection(sentBoxCol.value()); QVERIFY(sourceBox); QVERIFY(!sourceBox->sourceCollections().contains(sentSourceCol->id())); QVERIFY(!changeRecorder.collectionsMonitored().contains(sentSourceCol.value())); QVERIFY(!manager.unifiedMailboxForSource(sentSourceCol->id())); // Lets demote the other source collection now, that should remove the unified box completely sentSourceCol = collectionForRid(QStringLiteral("res2_sent")); QVERIFY(sentSourceCol); sentSourceCol->attribute()->setCollectionType("drafts"); modify = new Akonadi::CollectionModifyJob(sentSourceCol.value(), this); AKVERIFYEXEC(modify); // Wait for the change recorder once again QVERIFY(crChangedSpy.wait()); QTest::qWait(0); // There's no more Sent unified box QVERIFY(!manager.unifiedMailboxFromCollection(sentBoxCol.value())); // The collection is still monitored: it belongs to the Drafts special box now! QVERIFY(changeRecorder.collectionsMonitored().contains(sentSourceCol.value())); QVERIFY(manager.unifiedMailboxForSource(sentSourceCol->id())); } - }; QTEST_AKONADIMAIN(UnifiedMailboxManagerTest) #include "unifiedmailboxmanagertest.moc" diff --git a/agents/unifiedmailboxagent/common.h b/agents/unifiedmailboxagent/common.h index 6c2a99607..7f5a13ad6 100644 --- a/agents/unifiedmailboxagent/common.h +++ b/agents/unifiedmailboxagent/common.h @@ -1,41 +1,39 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef COMMON_H #define COMMON_H #include namespace Common { - static const auto MailMimeType = QStringLiteral("message/rfc822"); static const auto InboxBoxId = QStringLiteral("inbox"); static const auto SentBoxId = QStringLiteral("sent-mail"); static const auto DraftsBoxId = QStringLiteral("drafts"); static constexpr auto SpecialCollectionInbox = "inbox"; static constexpr auto SpecialCollectionSentMail = "send-mail"; static constexpr auto SpecialCollectionDrafts = "drafts"; static const auto AgentIdentifier = QStringLiteral("akonadi_unifiedmailbox_agent"); - } #endif diff --git a/agents/unifiedmailboxagent/mailkernel.cpp b/agents/unifiedmailboxagent/mailkernel.cpp index d8164320b..79453056c 100644 --- a/agents/unifiedmailboxagent/mailkernel.cpp +++ b/agents/unifiedmailboxagent/mailkernel.cpp @@ -1,144 +1,143 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "mailkernel.h" #include #include #include #include #include #include #include #include #include MailKernel::MailKernel(const KSharedConfigPtr &config, QObject *parent) : QObject(parent) , mConfig(config) { mMessageSender = new MessageComposer::AkonadiSender(this); mIdentityManager = new KIdentityManagement::IdentityManager(true, this); Akonadi::Session *session = new Akonadi::Session("UnifiedMailbox Kernel ETM", this); mFolderCollectionMonitor = new MailCommon::FolderCollectionMonitor(session, this); 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); CommonKernel->registerKernelIf(this); CommonKernel->registerSettingsIf(this); } MailKernel::~MailKernel() { CommonKernel->registerKernelIf(nullptr); CommonKernel->registerSettingsIf(nullptr); } KIdentityManagement::IdentityManager *MailKernel::identityManager() { return mIdentityManager; } MessageComposer::MessageSender *MailKernel::msgSender() { return mMessageSender; } Akonadi::EntityMimeTypeFilterModel *MailKernel::collectionModel() const { return mCollectionModel; } KSharedConfig::Ptr MailKernel::config() { return mConfig; } void MailKernel::syncConfig() { Q_ASSERT(false); } MailCommon::JobScheduler *MailKernel::jobScheduler() const { Q_ASSERT(false); return nullptr; } Akonadi::ChangeRecorder *MailKernel::folderCollectionMonitor() const { return mFolderCollectionMonitor->monitor(); } void MailKernel::updateSystemTray() { Q_ASSERT(false); } bool MailKernel::showPopupAfterDnD() { return false; } qreal MailKernel::closeToQuotaThreshold() { return 80; } QStringList MailKernel::customTemplates() { Q_ASSERT(false); return QStringList(); } bool MailKernel::excludeImportantMailFromExpiry() { Q_ASSERT(false); return true; } Akonadi::Collection::Id MailKernel::lastSelectedFolder() { Q_ASSERT(false); return Akonadi::Collection::Id(); } void MailKernel::setLastSelectedFolder(Akonadi::Collection::Id col) { Q_UNUSED(col); } void MailKernel::expunge(Akonadi::Collection::Id id, bool sync) { Akonadi::Collection col(id); if (col.isValid()) { mFolderCollectionMonitor->expunge(Akonadi::Collection(col), sync); } } - diff --git a/agents/unifiedmailboxagent/mailkernel.h b/agents/unifiedmailboxagent/mailkernel.h index 79972333e..381951711 100644 --- a/agents/unifiedmailboxagent/mailkernel.h +++ b/agents/unifiedmailboxagent/mailkernel.h @@ -1,74 +1,71 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef MAILKERNEL_H #define MAILKERNEL_H #include #include namespace Akonadi { class EntityTreeModel; class EntityMimeTypeFilterModel; } namespace MailCommon { class FolderCollectionMonitor; } -class MailKernel : public QObject - , public MailCommon::IKernel - , public MailCommon::ISettings +class MailKernel : public QObject, public MailCommon::IKernel, public MailCommon::ISettings { Q_OBJECT public: explicit MailKernel(const KSharedConfigPtr &config, QObject *parent = nullptr); ~MailKernel() override; KIdentityManagement::IdentityManager *identityManager() override; MessageComposer::MessageSender *msgSender() override; Akonadi::EntityMimeTypeFilterModel *collectionModel() const override; KSharedConfig::Ptr config() override; void syncConfig() override; MailCommon::JobScheduler *jobScheduler() const override; Akonadi::ChangeRecorder *folderCollectionMonitor() const override; void updateSystemTray() override; qreal closeToQuotaThreshold() override; bool excludeImportantMailFromExpiry() override; QStringList customTemplates() override; Akonadi::Collection::Id lastSelectedFolder() override; void setLastSelectedFolder(Akonadi::Collection::Id col) override; bool showPopupAfterDnD() override; void expunge(Akonadi::Collection::Id id, bool sync) override; private: Q_DISABLE_COPY(MailKernel) KSharedConfigPtr mConfig; KIdentityManagement::IdentityManager *mIdentityManager = nullptr; MessageComposer::MessageSender *mMessageSender = nullptr; MailCommon::FolderCollectionMonitor *mFolderCollectionMonitor = nullptr; Akonadi::EntityTreeModel *mEntityTreeModel = nullptr; Akonadi::EntityMimeTypeFilterModel *mCollectionModel = nullptr; }; #endif - diff --git a/agents/unifiedmailboxagent/settingsdialog.cpp b/agents/unifiedmailboxagent/settingsdialog.cpp index 3f42eb6a3..c44c29ae2 100644 --- a/agents/unifiedmailboxagent/settingsdialog.cpp +++ b/agents/unifiedmailboxagent/settingsdialog.cpp @@ -1,162 +1,158 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "settingsdialog.h" #include "unifiedmailboxmanager.h" #include "unifiedmailboxeditor.h" #include "unifiedmailbox.h" #include "mailkernel.h" #include #include #include #include #include #include #include #include #include #include #include namespace { - static constexpr const char *DialogGroup = "UnifiedMailboxSettingsDialog"; - } SettingsDialog::SettingsDialog(const KSharedConfigPtr &config, UnifiedMailboxManager &boxManager, WId, QWidget *parent) : QDialog(parent) , mBoxManager(boxManager) , mKernel(new MailKernel(config, this)) , mConfig(config) { setWindowIcon(QIcon::fromTheme(QStringLiteral("kmail"))); auto l = new QVBoxLayout(this); auto h = new QHBoxLayout; l->addLayout(h); mBoxModel = new QStandardItemModel(this); auto view = new QListView(this); view->setEditTriggers(QListView::NoEditTriggers); view->setModel(mBoxModel); h->addWidget(view); auto v = new QVBoxLayout; h->addLayout(v); auto addButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add-symbolic")), i18n("Add")); v->addWidget(addButton); connect(addButton, &QPushButton::clicked, this, [this]() { - auto mailbox = std::make_unique(); - auto editor = new UnifiedMailboxEditor(mailbox.get(), mConfig, this); - if (editor->exec()) { - mailbox->setId(mailbox->name()); // assign ID - addBox(mailbox.get()); - mBoxManager.insertBox(std::move(mailbox)); - } - }); + auto mailbox = std::make_unique(); + auto editor = new UnifiedMailboxEditor(mailbox.get(), mConfig, this); + if (editor->exec()) { + mailbox->setId(mailbox->name()); // assign ID + addBox(mailbox.get()); + mBoxManager.insertBox(std::move(mailbox)); + } + }); auto editButton = new QPushButton(QIcon::fromTheme(QStringLiteral("entry-edit")), i18n("Modify")); editButton->setEnabled(false); v->addWidget(editButton); connect(editButton, &QPushButton::clicked, this, [this, view]() { - const auto indexes = view->selectionModel()->selectedIndexes(); - if (!indexes.isEmpty()) { - auto item = mBoxModel->itemFromIndex(indexes[0]); - auto mailbox = item->data().value(); - auto editor = new UnifiedMailboxEditor(mailbox, mConfig, this); - if (editor->exec()) { - item->setText(mailbox->name()); - item->setIcon(QIcon::fromTheme(mailbox->icon())); - } - } - }); + const auto indexes = view->selectionModel()->selectedIndexes(); + if (!indexes.isEmpty()) { + auto item = mBoxModel->itemFromIndex(indexes[0]); + auto mailbox = item->data().value(); + auto editor = new UnifiedMailboxEditor(mailbox, mConfig, this); + if (editor->exec()) { + item->setText(mailbox->name()); + item->setIcon(QIcon::fromTheme(mailbox->icon())); + } + } + }); auto removeButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove-symbolic")), i18n("Remove")); removeButton->setEnabled(false); v->addWidget(removeButton); connect(removeButton, &QPushButton::clicked, this, [this, view]() { - const auto indexes = view->selectionModel()->selectedIndexes(); - if (!indexes.isEmpty()) { - auto item = mBoxModel->itemFromIndex(indexes[0]); - const auto mailbox = item->data().value(); - if (KMessageBox::warningYesNo( - this, i18n("Do you really want to remove unified mailbox %1?", mailbox->name()), - i18n("Really Remove?"), KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::Yes) - { - mBoxModel->removeRow(item->row()); - mBoxManager.removeBox(mailbox->id()); - } - } - }); + const auto indexes = view->selectionModel()->selectedIndexes(); + if (!indexes.isEmpty()) { + auto item = mBoxModel->itemFromIndex(indexes[0]); + const auto mailbox = item->data().value(); + if (KMessageBox::warningYesNo( + this, i18n("Do you really want to remove unified mailbox %1?", mailbox->name()), + i18n("Really Remove?"), KStandardGuiItem::remove(), KStandardGuiItem::cancel()) == KMessageBox::Yes) { + mBoxModel->removeRow(item->row()); + mBoxManager.removeBox(mailbox->id()); + } + } + }); v->addStretch(1); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, [view, editButton, removeButton]() { - const bool hasSelection = view->selectionModel()->hasSelection(); - editButton->setEnabled(hasSelection); - removeButton->setEnabled(hasSelection); - }); + const bool hasSelection = view->selectionModel()->hasSelection(); + editButton->setEnabled(hasSelection); + removeButton->setEnabled(hasSelection); + }); auto box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(box, &QDialogButtonBox::accepted, this, &SettingsDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &SettingsDialog::reject); l->addWidget(box); loadBoxes(); const auto dlgGroup = config->group(DialogGroup); if (dlgGroup.hasKey("geometry")) { restoreGeometry(dlgGroup.readEntry("geometry", QByteArray())); } else { resize(500, 500); } - } SettingsDialog::~SettingsDialog() { auto dlgGroup = mConfig->group(DialogGroup); dlgGroup.writeEntry("geometry", saveGeometry()); } void SettingsDialog::accept() { mBoxManager.saveBoxes(); QDialog::accept(); } void SettingsDialog::loadBoxes() { mBoxModel->clear(); for (const auto &mailboxIt : mBoxManager) { addBox(mailboxIt.second.get()); } } void SettingsDialog::addBox(UnifiedMailbox *box) { auto item = new QStandardItem(QIcon::fromTheme(box->icon()), box->name()); item->setData(QVariant::fromValue(box)); mBoxModel->appendRow(item); } diff --git a/agents/unifiedmailboxagent/settingsdialog.h b/agents/unifiedmailboxagent/settingsdialog.h index 29fbaf94f..07600fc65 100644 --- a/agents/unifiedmailboxagent/settingsdialog.h +++ b/agents/unifiedmailboxagent/settingsdialog.h @@ -1,56 +1,53 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef SETTINGSDIALOG_H_ #define SETTINGSDIALOG_H_ #include #include #include "unifiedmailboxmanager.h" class QStandardItemModel; class MailKernel; class SettingsDialog : public QDialog { Q_OBJECT public: - explicit SettingsDialog(const KSharedConfigPtr &config, UnifiedMailboxManager &manager, - WId windowId, QWidget *parent = nullptr); + explicit SettingsDialog(const KSharedConfigPtr &config, UnifiedMailboxManager &manager, WId windowId, QWidget *parent = nullptr); ~SettingsDialog() override; public Q_SLOTS: void accept() override; private: void loadBoxes(); void addBox(UnifiedMailbox *box); private: QStandardItemModel *mBoxModel = nullptr; UnifiedMailboxManager &mBoxManager; MailKernel *mKernel = nullptr; KSharedConfigPtr mConfig; }; - - #endif diff --git a/agents/unifiedmailboxagent/unifiedmailbox.cpp b/agents/unifiedmailboxagent/unifiedmailbox.cpp index 3ac497a71..7b5513dfb 100644 --- a/agents/unifiedmailboxagent/unifiedmailbox.cpp +++ b/agents/unifiedmailboxagent/unifiedmailbox.cpp @@ -1,155 +1,152 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "unifiedmailbox.h" #include "unifiedmailboxmanager.h" #include "utils.h" #include "common.h" #include - bool UnifiedMailbox::operator==(const UnifiedMailbox &other) const { return mId == other.mId; } void UnifiedMailbox::load(const KConfigGroup &group) { mId = group.name(); mName = group.readEntry("name"); mIcon = group.readEntry("icon", QStringLiteral("folder-mail")); mSources = listToSet(group.readEntry("sources", QList{})); // This is not authoritative, we will do collection discovery anyway mCollectionId = group.readEntry("collectionId", -1ll); } -void UnifiedMailbox::save(KConfigGroup& group) const +void UnifiedMailbox::save(KConfigGroup &group) const { group.writeEntry("name", mName); group.writeEntry("icon", mIcon); group.writeEntry("sources", setToList(mSources)); // just for caching, we will do collection discovery on next start anyway if (mCollectionId) { group.writeEntry("collectionId", *mCollectionId); } else { group.deleteEntry("collectionId"); } } bool UnifiedMailbox::isSpecial() const { return mId == Common::InboxBoxId - || mId == Common::SentBoxId - || mId == Common::DraftsBoxId; + || mId == Common::SentBoxId + || mId == Common::DraftsBoxId; } void UnifiedMailbox::setCollectionId(qint64 id) { mCollectionId = id; } stdx::optional UnifiedMailbox::collectionId() const { return mCollectionId; } void UnifiedMailbox::setId(const QString &id) { mId = id; } QString UnifiedMailbox::id() const { return mId; } void UnifiedMailbox::setName(const QString &name) { mName = name; } QString UnifiedMailbox::name() const { return mName; } void UnifiedMailbox::setIcon(const QString &icon) { mIcon = icon; } QString UnifiedMailbox::icon() const { return mIcon; } void UnifiedMailbox::addSourceCollection(qint64 source) { mSources.insert(source); if (mManager) { mManager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}); mManager->mSourceToBoxMap.insert({ source, this }); } } void UnifiedMailbox::removeSourceCollection(qint64 source) { mSources.remove(source); if (mManager) { mManager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}, false); mManager->mSourceToBoxMap.erase(source); } } void UnifiedMailbox::setSourceCollections(const QSet &sources) { while (!mSources.empty()) { removeSourceCollection(*mSources.begin()); } for (auto source : sources) { addSourceCollection(source); } } QSet UnifiedMailbox::sourceCollections() const { return mSources; } void UnifiedMailbox::attachManager(UnifiedMailboxManager *manager) { if (mManager != manager) { if (manager) { // Force that we start monitoring all the collections for (auto source : mSources) { manager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}); manager->mSourceToBoxMap.insert({ source, this }); } } else { for (auto source : mSources) { mManager->mMonitor.setCollectionMonitored(Akonadi::Collection{source}, false); mManager->mSourceToBoxMap.erase(source); } } mManager = manager; } } - - diff --git a/agents/unifiedmailboxagent/unifiedmailbox.h b/agents/unifiedmailboxagent/unifiedmailbox.h index f241c90e7..43d07e972 100644 --- a/agents/unifiedmailboxagent/unifiedmailbox.h +++ b/agents/unifiedmailboxagent/unifiedmailbox.h @@ -1,83 +1,82 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef UNIFIEDMAILBOX_H #define UNIFIEDMAILBOX_H #include #include #include #include "utils.h" class KConfigGroup; class UnifiedMailboxManager; class UnifiedMailbox { friend class UnifiedMailboxManager; public: UnifiedMailbox() = default; UnifiedMailbox(UnifiedMailbox &&) = default; UnifiedMailbox &operator=(UnifiedMailbox &&) = default; UnifiedMailbox(const UnifiedMailbox &) = delete; UnifiedMailbox &operator=(const UnifiedMailbox &) = delete; /** Compares two boxes by their ID **/ bool operator==(const UnifiedMailbox &other) const; void save(KConfigGroup &group) const; void load(const KConfigGroup &group); bool isSpecial() const; stdx::optional collectionId() const; void setCollectionId(qint64 id); QString id() const; void setId(const QString &id); QString name() const; void setName(const QString &name); QString icon() const; void setIcon(const QString &icon); void addSourceCollection(qint64 source); void removeSourceCollection(qint64 source); void setSourceCollections(const QSet &sources); QSet sourceCollections() const; private: void attachManager(UnifiedMailboxManager *manager); stdx::optional mCollectionId; QString mId; QString mName; QString mIcon; QSet mSources; UnifiedMailboxManager *mManager = nullptr; }; -Q_DECLARE_METATYPE(UnifiedMailbox*) - +Q_DECLARE_METATYPE(UnifiedMailbox *) #endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp index 81dbddf3c..9650cb54c 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxagent.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxagent.cpp @@ -1,292 +1,287 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "unifiedmailboxagent.h" #include "unifiedmailbox.h" #include "unifiedmailboxagent_debug.h" #include "unifiedmailboxagentadaptor.h" #include "settingsdialog.h" #include "settings.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - UnifiedMailboxAgent::UnifiedMailboxAgent(const QString &id) : Akonadi::ResourceBase(id) , mBoxManager(config()) { setAgentName(i18n("Unified Mailboxes")); new UnifiedMailboxAgentAdaptor(this); KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/UnifiedMailboxAgent"), this, QDBusConnection::ExportAdaptors); const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, identifier()); KDBusConnectionPool::threadConnection().registerService(service); connect(&mBoxManager, &UnifiedMailboxManager::updateBox, this, [this](const UnifiedMailbox *box) { - if (!box->collectionId()) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "MailboxManager wants us to update Box but does not have its CollectionId!?"; - return; - } + if (!box->collectionId()) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "MailboxManager wants us to update Box but does not have its CollectionId!?"; + return; + } - // Schedule collection sync for the box - synchronizeCollection(box->collectionId().value()); - }); + // Schedule collection sync for the box + synchronizeCollection(box->collectionId().value()); + }); auto &ifs = changeRecorder()->itemFetchScope(); ifs.setAncestorRetrieval(Akonadi::ItemFetchScope::None); ifs.setCacheOnly(true); ifs.fetchFullPayload(false); if (Settings::self()->enabled()) { QTimer::singleShot(0, this, &UnifiedMailboxAgent::delayedInit); } } void UnifiedMailboxAgent::configure(WId windowId) { QPointer agent(this); if (SettingsDialog(config(), mBoxManager, windowId).exec() && agent) { mBoxManager.saveBoxes(); synchronize(); Q_EMIT configurationDialogAccepted(); } else { mBoxManager.loadBoxes(); } } void UnifiedMailboxAgent::delayedInit() { qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "delayed init"; fixSpecialCollections(); mBoxManager.loadBoxes([this]() { // boxes loaded, let's sync up synchronize(); }); } - bool UnifiedMailboxAgent::enabledAgent() const { return Settings::self()->enabled(); } void UnifiedMailboxAgent::setEnableAgent(bool enabled) { if (enabled != Settings::self()->enabled()) { Settings::self()->setEnabled(enabled); Settings::self()->save(); if (!enabled) { setOnline(false); auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); fetch->fetchScope().setResource(identifier()); connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &cols) { - for (const auto &col : cols) { - new Akonadi::CollectionDeleteJob(col, this); - } - }); + for (const auto &col : cols) { + new Akonadi::CollectionDeleteJob(col, this); + } + }); } else { setOnline(true); delayedInit(); } } } - void UnifiedMailboxAgent::retrieveCollections() { if (!Settings::self()->enabled()) { collectionsRetrieved({}); return; } Akonadi::Collection::List collections; Akonadi::Collection topLevel; topLevel.setName(identifier()); topLevel.setRemoteId(identifier()); topLevel.setParentCollection(Akonadi::Collection::root()); topLevel.setContentMimeTypes({Akonadi::Collection::mimeType()}); topLevel.setRights(Akonadi::Collection::ReadOnly); auto displayAttr = topLevel.attribute(Akonadi::Collection::AddIfMissing); displayAttr->setDisplayName(i18n("Unified Mailboxes")); displayAttr->setActiveIconName(QStringLiteral("globe")); collections.push_back(topLevel); for (const auto &boxIt : mBoxManager) { const auto &box = boxIt.second; Akonadi::Collection col; col.setName(box->id()); col.setRemoteId(box->id()); col.setParentCollection(topLevel); col.setContentMimeTypes({Common::MailMimeType}); col.setRights(Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanDeleteItem); col.setVirtual(true); auto displayAttr = col.attribute(Akonadi::Collection::AddIfMissing); displayAttr->setDisplayName(box->name()); displayAttr->setIconName(box->icon()); collections.push_back(std::move(col)); } collectionsRetrieved(std::move(collections)); // Add mapping between boxes and collections mBoxManager.discoverBoxCollections(); } void UnifiedMailboxAgent::retrieveItems(const Akonadi::Collection &c) { if (!Settings::self()->enabled()) { itemsRetrieved({}); return; } // First check that we have all Items from all source collections Q_EMIT status(Running, i18n("Synchronizing unified mailbox %1", c.displayName())); const auto unifiedBox = mBoxManager.unifiedMailboxFromCollection(c); if (!unifiedBox) { qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Failed to retrieve box ID for collection " << c.id(); itemsRetrievedIncremental({}, {}); // fake incremental retrieval return; } const auto lastSeenEvent = QDateTime::fromSecsSinceEpoch(c.remoteRevision().toLongLong()); const auto sources = unifiedBox->sourceCollections(); for (auto source : sources) { auto fetch = new Akonadi::ItemFetchJob(Akonadi::Collection(source), this); fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); fetch->fetchScope().setFetchVirtualReferences(true); fetch->fetchScope().setCacheOnly(true); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, [this, c](const Akonadi::Item::List &items) { - Akonadi::Item::List toLink; - std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toLink), - [&c](const Akonadi::Item &item) { - return !item.virtualReferences().contains(c); - }); - if (!toLink.isEmpty()) { - new Akonadi::LinkJob(c, toLink, this); - } - }); + Akonadi::Item::List toLink; + std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toLink), + [&c](const Akonadi::Item &item) { + return !item.virtualReferences().contains(c); + }); + if (!toLink.isEmpty()) { + new Akonadi::LinkJob(c, toLink, this); + } + }); } auto fetch = new Akonadi::ItemFetchJob(c, this); fetch->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches); fetch->fetchScope().setCacheOnly(true); fetch->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, [this, unifiedBox, c](const Akonadi::Item::List &items) { - Akonadi::Item::List toUnlink; - std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toUnlink), - [&unifiedBox](const Akonadi::Item &item) { - return !unifiedBox->sourceCollections().contains(item.storageCollectionId()); - }); - if (!toUnlink.isEmpty()) { - new Akonadi::UnlinkJob(c, toUnlink, this); - } - }); + Akonadi::Item::List toUnlink; + std::copy_if(items.cbegin(), items.cend(), std::back_inserter(toUnlink), + [&unifiedBox](const Akonadi::Item &item) { + return !unifiedBox->sourceCollections().contains(item.storageCollectionId()); + }); + if (!toUnlink.isEmpty()) { + new Akonadi::UnlinkJob(c, toUnlink, this); + } + }); connect(fetch, &Akonadi::ItemFetchJob::result, this, [this]() { - itemsRetrievedIncremental({}, {}); // fake incremental retrieval - }); + itemsRetrievedIncremental({}, {}); // fake incremental retrieval + }); } bool UnifiedMailboxAgent::retrieveItem(const Akonadi::Item &item, const QSet &parts) { // This method should never be called by Akonadi Q_UNUSED(parts); qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "retrieveItem() for item" << item.id() << "called but we can't own any items! This is a bug in Akonadi"; return false; } - void UnifiedMailboxAgent::fixSpecialCollection(const QString &colId, Akonadi::SpecialMailCollections::Type type) { if (colId.isEmpty()) { return; } const auto id = colId.toLongLong(); // SpecialMailCollection requires the Collection to have a Resource set as well, so // we have to retrieve it first. connect(new Akonadi::CollectionFetchJob(Akonadi::Collection(id), Akonadi::CollectionFetchJob::Base, this), &Akonadi::CollectionFetchJob::collectionsReceived, this, [type](const Akonadi::Collection::List &cols) { - if (cols.count() != 1) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Identity special collection retrieval did not find a valid collection"; - return; - } - Akonadi::SpecialMailCollections::self()->registerCollection(type, cols.first()); - }); + if (cols.count() != 1) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Identity special collection retrieval did not find a valid collection"; + return; + } + Akonadi::SpecialMailCollections::self()->registerCollection(type, cols.first()); + }); } void UnifiedMailboxAgent::fixSpecialCollections() { // This is a tiny hack to assign proper SpecialCollectionAttribute to special collections // assigned trough Identities. This should happen automatically in KMail when user changes // the special collections on the identity page, but until recent master (2018-07-24) this // wasn't the case and there's no automatic migration, so we need to fix up manually here. if (Settings::self()->fixedSpecialCollections()) { return; } qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Fixing special collections assigned from Identities"; for (const auto &identity : *KIdentityManagement::IdentityManager::self()) { if (!identity.disabledFcc()) { fixSpecialCollection(identity.fcc(), Akonadi::SpecialMailCollections::SentMail); } fixSpecialCollection(identity.drafts(), Akonadi::SpecialMailCollections::Drafts); fixSpecialCollection(identity.templates(), Akonadi::SpecialMailCollections::Templates); } Settings::self()->setFixedSpecialCollections(true); } - AKONADI_RESOURCE_MAIN(UnifiedMailboxAgent) diff --git a/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp index 52c9af119..eb85b20dd 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.cpp @@ -1,185 +1,183 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "unifiedmailboxeditor.h" #include "unifiedmailbox.h" #include "mailkernel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { - static constexpr const char *EditorGroup = "UnifiedMailboxEditorDialog"; class SelfFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit SelfFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) - {} + { + } QVariant data(const QModelIndex &index, int role) const override { if (role == Qt::CheckStateRole) { // Make top-level collections uncheckable const Akonadi::Collection col = data(index, Akonadi::EntityTreeModel::CollectionRole).value(); if (col.parentCollection() == Akonadi::Collection::root()) { return {}; } } return QSortFilterProxyModel::data(index, role); } Qt::ItemFlags flags(const QModelIndex &index) const override { // Make top-level collections uncheckable const Akonadi::Collection col = data(index, Akonadi::EntityTreeModel::CollectionRole).value(); if (col.parentCollection() == Akonadi::Collection::root()) { return QSortFilterProxyModel::flags(index) & ~Qt::ItemIsUserCheckable; } else { return QSortFilterProxyModel::flags(index); } } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { // Hide ourselves const auto sourceIndex = sourceModel()->index(source_row, 0, source_parent); const Akonadi::Collection col = sourceModel()->data(sourceIndex, Akonadi::EntityTreeModel::CollectionRole).value(); return !UnifiedMailboxManager::isUnifiedMailbox(col); } }; - } -UnifiedMailboxEditor::UnifiedMailboxEditor(const KSharedConfigPtr &config, QWidget* parent) +UnifiedMailboxEditor::UnifiedMailboxEditor(const KSharedConfigPtr &config, QWidget *parent) : UnifiedMailboxEditor({}, config, parent) { } UnifiedMailboxEditor::UnifiedMailboxEditor(UnifiedMailbox *mailbox, const KSharedConfigPtr &config, QWidget *parent) : QDialog(parent) , mMailbox(mailbox) , mConfig(config) { setWindowIcon(QIcon::fromTheme(QStringLiteral("kmail"))); auto l = new QVBoxLayout(this); auto f = new QFormLayout; l->addLayout(f); auto nameEdit = new QLineEdit(mMailbox->name()); nameEdit->setClearButtonEnabled(true); f->addRow(i18n("Name:"), nameEdit); connect(nameEdit, &QLineEdit::textChanged, this, [this](const QString &name) { - mMailbox->setName(name.trimmed()); - }); + mMailbox->setName(name.trimmed()); + }); auto iconButton = new QPushButton(QIcon::fromTheme(mMailbox->icon(), QIcon::fromTheme(QStringLiteral("folder-mail"))), - i18n("Pick icon...")); + i18n("Pick icon...")); f->addRow(i18n("Icon:"), iconButton); connect(iconButton, &QPushButton::clicked, this, [iconButton, this]() { - const auto iconName = KIconDialog::getIcon(); - if (!iconName.isEmpty()) { - mMailbox->setIcon(iconName); - iconButton->setIcon(QIcon::fromTheme(iconName)); - } - }); + const auto iconName = KIconDialog::getIcon(); + if (!iconName.isEmpty()) { + mMailbox->setIcon(iconName); + iconButton->setIcon(QIcon::fromTheme(iconName)); + } + }); mMailbox->setIcon(iconButton->icon().name()); l->addSpacing(10); auto ftw = new MailCommon::FolderTreeWidget(nullptr, nullptr, - MailCommon::FolderTreeWidget::TreeViewOptions(MailCommon::FolderTreeWidget::UseDistinctSelectionModel | - MailCommon::FolderTreeWidget::HideStatistics)); + MailCommon::FolderTreeWidget::TreeViewOptions(MailCommon::FolderTreeWidget::UseDistinctSelectionModel + |MailCommon::FolderTreeWidget::HideStatistics)); l->addWidget(ftw); auto ftv = ftw->folderTreeView(); auto sourceModel = ftv->model(); auto selectionModel = ftw->selectionModel(); auto checkable = new KCheckableProxyModel(this); checkable->setSourceModel(sourceModel); checkable->setSelectionModel(selectionModel); const auto sources = mMailbox->sourceCollections(); for (const auto source : sources) { const auto index = Akonadi::EntityTreeModel::modelIndexForCollection(selectionModel->model(), Akonadi::Collection(source)); selectionModel->select(index, QItemSelectionModel::Select); } connect(checkable->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected) { - auto indexes = selected.indexes(); - for (const auto &index : indexes) { - mMailbox->addSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); - } - indexes = deselected.indexes(); - for (const auto &index : indexes) { - mMailbox->removeSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); - } - }); + auto indexes = selected.indexes(); + for (const auto &index : indexes) { + mMailbox->addSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); + } + indexes = deselected.indexes(); + for (const auto &index : indexes) { + mMailbox->removeSourceCollection(index.data(Akonadi::EntityTreeModel::CollectionIdRole).toLongLong()); + } + }); auto selfFilter = new SelfFilterProxyModel(this); selfFilter->setSourceModel(checkable); ftv->setModel(selfFilter); ftv->expandAll(); auto box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(nameEdit, &QLineEdit::textChanged, box, [box](const QString &name) { - box->button(QDialogButtonBox::Ok)->setEnabled(!name.trimmed().isEmpty()); - }); + box->button(QDialogButtonBox::Ok)->setEnabled(!name.trimmed().isEmpty()); + }); box->button(QDialogButtonBox::Ok)->setEnabled(!nameEdit->text().trimmed().isEmpty()); l->addWidget(box); const auto editorGroup = config->group(EditorGroup); if (editorGroup.hasKey("geometry")) { restoreGeometry(editorGroup.readEntry("geometry", QByteArray())); } else { resize(500, 900); } } UnifiedMailboxEditor::~UnifiedMailboxEditor() { auto editorGrp = mConfig->group(EditorGroup); editorGrp.writeEntry("geometry", saveGeometry()); } - #include "unifiedmailboxeditor.moc" diff --git a/agents/unifiedmailboxagent/unifiedmailboxeditor.h b/agents/unifiedmailboxagent/unifiedmailboxeditor.h index 2a1b2c38b..c36845e89 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxeditor.h +++ b/agents/unifiedmailboxagent/unifiedmailboxeditor.h @@ -1,42 +1,41 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef UNIFIEDMAILBOXEDITOR_H #define UNIFIEDMAILBOXEDITOR_H #include #include "unifiedmailboxmanager.h" class UnifiedMailbox; class UnifiedMailboxEditor : public QDialog { Q_OBJECT public: explicit UnifiedMailboxEditor(const KSharedConfigPtr &config, QWidget *parent = nullptr); - explicit UnifiedMailboxEditor(UnifiedMailbox *mailbox, const KSharedConfigPtr &config, - QWidget *parent = nullptr); + explicit UnifiedMailboxEditor(UnifiedMailbox *mailbox, const KSharedConfigPtr &config, QWidget *parent = nullptr); ~UnifiedMailboxEditor() override; private: UnifiedMailbox *mMailbox = nullptr; KSharedConfigPtr mConfig; }; #endif diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp index 8eed49857..a0cb1c8ca 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.cpp @@ -1,433 +1,432 @@ /* Copyright (C) 2018 Daniel Vrátil 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 "unifiedmailboxmanager.h" #include "unifiedmailbox.h" #include "unifiedmailboxagent_debug.h" #include "utils.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include - namespace { - /** * A little RAII helper to make sure changeProcessed() and replayNext() gets * called on the ChangeRecorder whenever we are done with handling a change. */ class ReplayNextOnExit { public: ReplayNextOnExit(Akonadi::ChangeRecorder &recorder) : mRecorder(recorder) - {} + { + } + ~ReplayNextOnExit() { mRecorder.changeProcessed(); mRecorder.replayNext(); } + private: Akonadi::ChangeRecorder &mRecorder; }; - } // static bool UnifiedMailboxManager::isUnifiedMailbox(const Akonadi::Collection &col) { #ifdef UNIT_TESTS return col.parentCollection().name() == Common::AgentIdentifier; #else return col.resource() == Common::AgentIdentifier; #endif } -UnifiedMailboxManager::UnifiedMailboxManager(const KSharedConfigPtr &config, QObject* parent) +UnifiedMailboxManager::UnifiedMailboxManager(const KSharedConfigPtr &config, QObject *parent) : QObject(parent) , mConfig(config) { mMonitor.setObjectName(QStringLiteral("UnifiedMailboxChangeRecorder")); mMonitor.setConfig(&mMonitorSettings); mMonitor.setChangeRecordingEnabled(true); mMonitor.setTypeMonitored(Akonadi::Monitor::Items); mMonitor.setTypeMonitored(Akonadi::Monitor::Collections); mMonitor.itemFetchScope().setCacheOnly(true); mMonitor.itemFetchScope().setFetchRemoteIdentification(false); mMonitor.itemFetchScope().setFetchModificationTime(false); mMonitor.collectionFetchScope().fetchAttribute(); connect(&mMonitor, &Akonadi::Monitor::itemAdded, this, [this](const Akonadi::Item &item, const Akonadi::Collection &collection) { - ReplayNextOnExit replayNext(mMonitor); + ReplayNextOnExit replayNext(mMonitor); - qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Item" << item.id() << "added to collection" << collection.id(); - const auto box = unifiedMailboxForSource(collection.id()); - if (!box) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Failed to find unified mailbox for source collection " << collection.id(); - return; - } + qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Item" << item.id() << "added to collection" << collection.id(); + const auto box = unifiedMailboxForSource(collection.id()); + if (!box) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Failed to find unified mailbox for source collection " << collection.id(); + return; + } - if (!box->collectionId()) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Missing box->collection mapping for unified mailbox" << box->id(); - return; - } + if (!box->collectionId()) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Missing box->collection mapping for unified mailbox" << box->id(); + return; + } - new Akonadi::LinkJob(Akonadi::Collection{box->collectionId().value()}, {item}, this); - }); + new Akonadi::LinkJob(Akonadi::Collection{box->collectionId().value()}, {item}, this); + }); connect(&mMonitor, &Akonadi::Monitor::itemsRemoved, this, [this](const Akonadi::Item::List &items) { - ReplayNextOnExit replayNext(mMonitor); - - // Monitor did the heavy lifting for us and already figured out that - // we only monitor the source collection of the Items and translated - // it into REMOVE change. - - // This relies on Akonadi never mixing Items from different sources or - // destination during batch-moves. - const auto parentId = items.first().parentCollection().id(); - const auto box = unifiedMailboxForSource(parentId); - if (!box) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Received Remove notification for Items belonging to" << parentId << "which we don't monitor"; - return; - } - if (!box->collectionId()) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Missing box->collection mapping for unified mailbox" << box->id(); - return; - } + ReplayNextOnExit replayNext(mMonitor); + + // Monitor did the heavy lifting for us and already figured out that + // we only monitor the source collection of the Items and translated + // it into REMOVE change. + + // This relies on Akonadi never mixing Items from different sources or + // destination during batch-moves. + const auto parentId = items.first().parentCollection().id(); + const auto box = unifiedMailboxForSource(parentId); + if (!box) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Received Remove notification for Items belonging to" << parentId << "which we don't monitor"; + return; + } + if (!box->collectionId()) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Missing box->collection mapping for unified mailbox" << box->id(); + return; + } - new Akonadi::UnlinkJob(Akonadi::Collection{box->collectionId().value()}, items, this); - }); + new Akonadi::UnlinkJob(Akonadi::Collection{box->collectionId().value()}, items, this); + }); connect(&mMonitor, &Akonadi::Monitor::itemsMoved, this, [this](const Akonadi::Item::List &items, const Akonadi::Collection &srcCollection, const Akonadi::Collection &dstCollection) { - ReplayNextOnExit replayNext(mMonitor); + ReplayNextOnExit replayNext(mMonitor); - if (const auto srcBox = unifiedMailboxForSource(srcCollection.id())) { - // Move source collection was our source, unlink the Item from a box - new Akonadi::UnlinkJob(Akonadi::Collection{srcBox->collectionId().value()}, items, this); - } - if (const auto dstBox = unifiedMailboxForSource(dstCollection.id())) { - // Move destination collection is our source, link the Item into a box - new Akonadi::LinkJob(Akonadi::Collection{dstBox->collectionId().value()}, items, this); - } - }); + if (const auto srcBox = unifiedMailboxForSource(srcCollection.id())) { + // Move source collection was our source, unlink the Item from a box + new Akonadi::UnlinkJob(Akonadi::Collection{srcBox->collectionId().value()}, items, this); + } + if (const auto dstBox = unifiedMailboxForSource(dstCollection.id())) { + // Move destination collection is our source, link the Item into a box + new Akonadi::LinkJob(Akonadi::Collection{dstBox->collectionId().value()}, items, this); + } + }); connect(&mMonitor, &Akonadi::Monitor::collectionRemoved, this, [this](const Akonadi::Collection &col) { - ReplayNextOnExit replayNext(mMonitor); - - if (auto box = unifiedMailboxForSource(col.id())) { - box->removeSourceCollection(col.id()); - mMonitor.setCollectionMonitored(col, false); - if (box->sourceCollections().isEmpty()) { - removeBox(box->id()); - } - saveBoxes(); - // No need to resync the box collection, the linked Items got removed by Akonadi - } else { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Received notification about removal of Collection" << col.id() << "which we don't monitor"; - } - }); + ReplayNextOnExit replayNext(mMonitor); + + if (auto box = unifiedMailboxForSource(col.id())) { + box->removeSourceCollection(col.id()); + mMonitor.setCollectionMonitored(col, false); + if (box->sourceCollections().isEmpty()) { + removeBox(box->id()); + } + saveBoxes(); + // No need to resync the box collection, the linked Items got removed by Akonadi + } else { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Received notification about removal of Collection" << col.id() << "which we don't monitor"; + } + }); connect(&mMonitor, qOverload &>(&Akonadi::Monitor::collectionChanged), this, [this](const Akonadi::Collection &col, const QSet &parts) { - ReplayNextOnExit replayNext(mMonitor); + ReplayNextOnExit replayNext(mMonitor); - qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Collection changed:" << parts; - if (!parts.contains(Akonadi::SpecialCollectionAttribute().type())) { - return; - } + qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Collection changed:" << parts; + if (!parts.contains(Akonadi::SpecialCollectionAttribute().type())) { + return; + } - if (col.hasAttribute()) { - const auto srcBox = unregisterSpecialSourceCollection(col.id()); - const auto dstBox = registerSpecialSourceCollection(col); - if (srcBox == dstBox) { - return; - } - - saveBoxes(); - - if (srcBox && srcBox->sourceCollections().isEmpty()) { - removeBox(srcBox->id()); - return; - } - - if (srcBox) { - Q_EMIT updateBox(srcBox); - } - if (dstBox) { - Q_EMIT updateBox(dstBox); - } + if (col.hasAttribute()) { + const auto srcBox = unregisterSpecialSourceCollection(col.id()); + const auto dstBox = registerSpecialSourceCollection(col); + if (srcBox == dstBox) { + return; + } + + saveBoxes(); + + if (srcBox && srcBox->sourceCollections().isEmpty()) { + removeBox(srcBox->id()); + return; + } + + if (srcBox) { + Q_EMIT updateBox(srcBox); + } + if (dstBox) { + Q_EMIT updateBox(dstBox); + } + } else { + if (const auto box = unregisterSpecialSourceCollection(col.id())) { + saveBoxes(); + if (box->sourceCollections().isEmpty()) { + removeBox(box->id()); } else { - if (const auto box = unregisterSpecialSourceCollection(col.id())) { - saveBoxes(); - if (box->sourceCollections().isEmpty()) { - removeBox(box->id()); - } else { - Q_EMIT updateBox(box); - } - } + Q_EMIT updateBox(box); } - }); - + } + } + }); } UnifiedMailboxManager::~UnifiedMailboxManager() { } Akonadi::ChangeRecorder &UnifiedMailboxManager::changeRecorder() { return mMonitor; } void UnifiedMailboxManager::loadBoxes(FinishedCallback &&finishedCb) { qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "loading boxes"; const auto group = mConfig->group("UnifiedMailboxes"); const auto boxGroups = group.groupList(); for (const auto &boxGroupName : boxGroups) { const auto boxGroup = group.group(boxGroupName); auto box = std::make_unique(); box->load(boxGroup); insertBox(std::move(box)); } const auto cb = [this, finishedCb = std::move(finishedCb)]() { - qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Finished callback: enabling change recorder"; - // Only now start processing changes from change recorder - connect(&mMonitor, &Akonadi::ChangeRecorder::changesAdded, &mMonitor, &Akonadi::ChangeRecorder::replayNext, Qt::QueuedConnection); - // And start replaying any potentially pending notification - QTimer::singleShot(0, &mMonitor, &Akonadi::ChangeRecorder::replayNext); - - if (finishedCb) { - finishedCb(); - } - }; + qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Finished callback: enabling change recorder"; + // Only now start processing changes from change recorder + connect(&mMonitor, &Akonadi::ChangeRecorder::changesAdded, &mMonitor, &Akonadi::ChangeRecorder::replayNext, Qt::QueuedConnection); + // And start replaying any potentially pending notification + QTimer::singleShot(0, &mMonitor, &Akonadi::ChangeRecorder::replayNext); + + if (finishedCb) { + finishedCb(); + } + }; qCDebug(UNIFIEDMAILBOXAGENT_LOG) << "Loaded" << mMailboxes.size() << "boxes from config"; if (mMailboxes.empty()) { createDefaultBoxes(std::move(cb)); } else { discoverBoxCollections(std::move(cb)); } } void UnifiedMailboxManager::saveBoxes() { auto group = mConfig->group("UnifiedMailboxes"); const auto currentGroups = group.groupList(); for (const auto &groupName : currentGroups) { group.deleteGroup(groupName); } for (const auto &boxIt : mMailboxes) { auto boxGroup = group.group(boxIt.second->id()); boxIt.second->save(boxGroup); } mConfig->sync(); } void UnifiedMailboxManager::insertBox(std::unique_ptr box) { auto it = mMailboxes.emplace(std::make_pair(box->id(), std::move(box))); it.first->second->attachManager(this); } void UnifiedMailboxManager::removeBox(const QString &id) { auto box = std::find_if(mMailboxes.begin(), mMailboxes.end(), - [&id](const std::pair> &box) { - return box.second->id() == id; - }); + [&id](const std::pair > &box) { + return box.second->id() == id; + }); if (box == mMailboxes.end()) { return; } box->second->attachManager(nullptr); mMailboxes.erase(box); } UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxForSource(qint64 source) const { const auto box = mSourceToBoxMap.find(source); if (box == mSourceToBoxMap.cend()) { return {}; } return box->second; } UnifiedMailbox *UnifiedMailboxManager::unifiedMailboxFromCollection(const Akonadi::Collection &col) const { if (!isUnifiedMailbox(col)) { return nullptr; } const auto box = mMailboxes.find(col.name()); if (box == mMailboxes.cend()) { return {}; } return box->second.get(); } void UnifiedMailboxManager::createDefaultBoxes(FinishedCallback &&finishedCb) { // First build empty boxes auto inbox = std::make_unique(); inbox->attachManager(this); inbox->setId(Common::InboxBoxId); inbox->setName(i18n("Inbox")); inbox->setIcon(QStringLiteral("mail-folder-inbox")); insertBox(std::move(inbox)); auto sent = std::make_unique(); sent->attachManager(this); sent->setId(Common::SentBoxId); sent->setName(i18n("Sent")); sent->setIcon(QStringLiteral("mail-folder-sent")); insertBox(std::move(sent)); auto drafts = std::make_unique(); drafts->attachManager(this); drafts->setId(Common::DraftsBoxId); drafts->setName(i18n("Drafts")); drafts->setIcon(QStringLiteral("document-properties")); insertBox(std::move(drafts)); auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); list->fetchScope().fetchAttribute(); list->fetchScope().setContentMimeTypes({QStringLiteral("message/rfc822")}); #ifdef UNIT_TESTS list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent); #else list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None); #endif connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &list) { - for (const auto &col : list) { - if (isUnifiedMailbox(col)) { - continue; - } - - try { - switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) { - case Akonadi::SpecialMailCollections::Inbox: - mMailboxes.at(Common::InboxBoxId)->addSourceCollection(col.id()); - break; - case Akonadi::SpecialMailCollections::SentMail: - mMailboxes.at(Common::SentBoxId)->addSourceCollection(col.id()); - break; - case Akonadi::SpecialMailCollections::Drafts: - mMailboxes.at(Common::DraftsBoxId)->addSourceCollection(col.id()); - break; - default: - continue; - } - } catch (const std::out_of_range &) { - qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Failed to find a special unified mailbox for source collection" << col.id(); - continue; - } + for (const auto &col : list) { + if (isUnifiedMailbox(col)) { + continue; + } + + try { + switch (Akonadi::SpecialMailCollections::self()->specialCollectionType(col)) { + case Akonadi::SpecialMailCollections::Inbox: + mMailboxes.at(Common::InboxBoxId)->addSourceCollection(col.id()); + break; + case Akonadi::SpecialMailCollections::SentMail: + mMailboxes.at(Common::SentBoxId)->addSourceCollection(col.id()); + break; + case Akonadi::SpecialMailCollections::Drafts: + mMailboxes.at(Common::DraftsBoxId)->addSourceCollection(col.id()); + break; + default: + continue; } - }); + } catch (const std::out_of_range &) { + qCWarning(UNIFIEDMAILBOXAGENT_LOG) << "Failed to find a special unified mailbox for source collection" << col.id(); + continue; + } + } + }); connect(list, &Akonadi::CollectionFetchJob::result, this, [this, finishedCb = std::move(finishedCb)]() { - saveBoxes(); - if (finishedCb) { - finishedCb(); - } - }); + saveBoxes(); + if (finishedCb) { + finishedCb(); + } + }); } void UnifiedMailboxManager::discoverBoxCollections(FinishedCallback &&finishedCb) { auto list = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, this); #ifdef UNIT_TESTS list->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent); #else list->fetchScope().setResource(Common::AgentIdentifier); #endif connect(list, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &list) { - for (const auto &col : list) { - if (!isUnifiedMailbox(col) || col.parentCollection() == Akonadi::Collection::root()) { - continue; - } + for (const auto &col : list) { + if (!isUnifiedMailbox(col) || col.parentCollection() == Akonadi::Collection::root()) { + continue; + } - mMailboxes.at(col.name())->setCollectionId(col.id()); - } - }); + mMailboxes.at(col.name())->setCollectionId(col.id()); + } + }); if (finishedCb) { connect(list, &Akonadi::CollectionFetchJob::result, this, finishedCb); } } -const UnifiedMailbox *UnifiedMailboxManager::registerSpecialSourceCollection(const Akonadi::Collection& col) +const UnifiedMailbox *UnifiedMailboxManager::registerSpecialSourceCollection(const Akonadi::Collection &col) { // This is slightly awkward, wold be better if we could use SpecialMailCollections, // but it also relies on Monitor internally, so there's a possible race condition // between our ChangeRecorder and SpecialMailCollections' Monitor auto attr = col.attribute(); Q_ASSERT(attr); if (!attr) { return {}; } decltype(mMailboxes)::iterator box; if (attr->collectionType() == Common::SpecialCollectionInbox) { box = mMailboxes.find(Common::InboxBoxId); } else if (attr->collectionType() == Common::SpecialCollectionSentMail) { box = mMailboxes.find(Common::SentBoxId); } else if (attr->collectionType() == Common::SpecialCollectionDrafts) { box = mMailboxes.find(Common::DraftsBoxId); } if (box == mMailboxes.end()) { return {}; } box->second->addSourceCollection(col.id()); return box->second.get(); } const UnifiedMailbox *UnifiedMailboxManager::unregisterSpecialSourceCollection(qint64 colId) { auto box = unifiedMailboxForSource(colId); if (!box) { return {}; } if (!box->isSpecial()) { qCDebug(UNIFIEDMAILBOXAGENT_LOG) << colId << "does not belong to a special unified box" << box->id(); return {}; } box->removeSourceCollection(colId); return box; } diff --git a/agents/unifiedmailboxagent/unifiedmailboxmanager.h b/agents/unifiedmailboxagent/unifiedmailboxmanager.h index e58160105..719109f5b 100644 --- a/agents/unifiedmailboxagent/unifiedmailboxmanager.h +++ b/agents/unifiedmailboxagent/unifiedmailboxmanager.h @@ -1,92 +1,92 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef UNIFIEDMAILBOXMANAGER_H #define UNIFIEDMAILBOXMANAGER_H #include "utils.h" #include #include #include #include #include #include #include #include class UnifiedMailbox; class UnifiedMailboxManager : public QObject { Q_OBJECT friend class UnifiedMailbox; public: - using FinishedCallback = std::function; - using Entry = std::pair>; + using FinishedCallback = std::function; + using Entry = std::pair >; explicit UnifiedMailboxManager(const KSharedConfigPtr &config, QObject *parent = nullptr); ~UnifiedMailboxManager() override; void loadBoxes(FinishedCallback &&cb = {}); void saveBoxes(); void discoverBoxCollections(FinishedCallback &&cb = {}); void insertBox(std::unique_ptr box); void removeBox(const QString &id); UnifiedMailbox *unifiedMailboxForSource(qint64 source) const; UnifiedMailbox *unifiedMailboxFromCollection(const Akonadi::Collection &col) const; inline auto begin() const { return mMailboxes.begin(); } inline auto end() const { return mMailboxes.end(); } static bool isUnifiedMailbox(const Akonadi::Collection &col); // Internal change recorder, for unittests Akonadi::ChangeRecorder &changeRecorder(); Q_SIGNALS: void updateBox(const UnifiedMailbox *box); private: void createDefaultBoxes(FinishedCallback &&cb); const UnifiedMailbox *unregisterSpecialSourceCollection(qint64 colId); const UnifiedMailbox *registerSpecialSourceCollection(const Akonadi::Collection &col); // Using std::unique_ptr because QScopedPointer is not movable // Using std::unordered_map because Qt containers do not support movable-only types, - std::unordered_map> mMailboxes; - std::unordered_map mSourceToBoxMap; + std::unordered_map > mMailboxes; + std::unordered_map mSourceToBoxMap; Akonadi::ChangeRecorder mMonitor; QSettings mMonitorSettings; KSharedConfigPtr mConfig; }; #endif diff --git a/agents/unifiedmailboxagent/utils.h b/agents/unifiedmailboxagent/utils.h index 85daf3347..26a2e2421 100644 --- a/agents/unifiedmailboxagent/utils.h +++ b/agents/unifiedmailboxagent/utils.h @@ -1,72 +1,73 @@ /* Copyright (C) 2018 Daniel Vrátil 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. */ #ifndef UTILS_H_ #define UTILS_H_ #include #include #include #include namespace stdx { - // Injects content of std::experimental namespace into "exp" namespace. - // C++ is magical. - using namespace std::experimental; +// Injects content of std::experimental namespace into "exp" namespace. +// C++ is magical. +using namespace std::experimental; } template inline QList setToList(QSet &&set) { QList rv; rv.reserve(set.size()); std::copy(set.cbegin(), set.cend(), std::back_inserter(rv)); return rv; } template inline QList setToList(const QSet &set) { QList rv; rv.reserve(set.size()); std::copy(set.cbegin(), set.cend(), std::back_inserter(rv)); return rv; } template inline QSet listToSet(QList &&list) { QSet rv; rv.reserve(list.size()); for (auto t : list) { rv.insert(std::move(t)); } return rv; } namespace std { - template<> - struct hash { - inline size_t operator()(const QString &str) const { - return qHash(str); - } - }; +template<> +struct hash { + inline size_t operator()(const QString &str) const + { + return qHash(str); + } +}; } #endif diff --git a/src/editor/attachment/attachmentcontroller.cpp b/src/editor/attachment/attachmentcontroller.cpp index 6a4b99b21..ccfeb66a5 100644 --- a/src/editor/attachment/attachmentcontroller.cpp +++ b/src/editor/attachment/attachmentcontroller.cpp @@ -1,164 +1,163 @@ /* * This file is part of KMail. * Copyright (c) 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * 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 "attachmentcontroller.h" #include "attachmentview.h" #include #include "settings/kmailsettings.h" #include "editor/kmcomposerwin.h" #include "kmkernel.h" #include "kmreadermainwin.h" #include "mailcommon/mailutil.h" #include #include #include #include #include "kmail_debug.h" #include #include #include using namespace KMail; using namespace KPIM; using namespace MailCommon; using namespace MessageCore; AttachmentController::AttachmentController(MessageComposer::AttachmentModel *model, AttachmentView *view, KMComposerWin *composer) : AttachmentControllerBase(model, composer, composer->actionCollection()) , mComposer(composer) , mView(view) { connect(composer, &KMComposerWin::identityChanged, this, &AttachmentController::identityChanged); connect(view, &AttachmentView::contextMenuRequested, this, &AttachmentControllerBase::showContextMenu); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AttachmentController::selectionChanged); connect(view, &QAbstractItemView::doubleClicked, this, &AttachmentController::doubleClicked); connect(this, &AttachmentController::refreshSelection, this, &AttachmentController::selectionChanged); connect(this, &AttachmentController::showAttachment, this, &AttachmentController::onShowAttachment); connect(this, &AttachmentController::selectedAllAttachment, this, &AttachmentController::slotSelectAllAttachment); connect(model, &MessageComposer::AttachmentModel::attachItemsRequester, this, &AttachmentController::addAttachmentItems); connect(this, &AttachmentController::actionsCreated, this, &AttachmentController::slotActionsCreated); } AttachmentController::~AttachmentController() { } void AttachmentController::slotSelectAllAttachment() { mView->selectAll(); } void AttachmentController::identityChanged() { const KIdentityManagement::Identity &identity = mComposer->identity(); // "Attach public key" is only possible if OpenPGP support is available: enableAttachPublicKey(QGpgME::openpgp()); // "Attach my public key" is only possible if OpenPGP support is // available and the user specified his key for the current identity: enableAttachMyPublicKey(QGpgME::openpgp() && !identity.pgpEncryptionKey().isEmpty()); } void AttachmentController::attachMyPublicKey() { const KIdentityManagement::Identity &identity = mComposer->identity(); qCDebug(KMAIL_LOG) << identity.identityName(); exportPublicKey(QString::fromLatin1(identity.pgpEncryptionKey())); } void AttachmentController::slotActionsCreated() { // Disable public key actions if appropriate. identityChanged(); // Disable actions like 'Remove', since nothing is currently selected. selectionChanged(); } void AttachmentController::addAttachmentItems(const Akonadi::Item::List &items) { Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(items, this); itemFetchJob->fetchScope().fetchFullPayload(true); itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(itemFetchJob, &Akonadi::ItemFetchJob::result, mComposer, &KMComposerWin::slotFetchJob); } void AttachmentController::selectionChanged() { const QModelIndexList selectedRows = mView->selectionModel()->selectedRows(); AttachmentPart::List selectedParts; selectedParts.reserve(selectedRows.count()); for (const QModelIndex &index : selectedRows) { AttachmentPart::Ptr part = mView->model()->data( index, MessageComposer::AttachmentModel::AttachmentPartRole).value(); selectedParts.append(part); } setSelectedParts(selectedParts); } void AttachmentController::onShowAttachment(KMime::Content *content, const QByteArray &charset) { - if (content->bodyAsMessage()) { KMime::Message::Ptr m(new KMime::Message); m->setContent(content->bodyAsMessage()->encodedContent()); m->parse(); KMReaderMainWin *win = new KMReaderMainWin(); win->showMessage(QString::fromLatin1(charset), m); win->show(); } else { KMReaderMainWin *win = new KMReaderMainWin(content, MessageViewer::Viewer::Text, QString::fromLatin1(charset)); win->show(); } } void AttachmentController::doubleClicked(const QModelIndex &itemClicked) { if (!itemClicked.isValid()) { qCDebug(KMAIL_LOG) << "Received an invalid item clicked index"; return; } // The itemClicked index will contain the column information. But we want to retrieve // the AttachmentPart, so we must recreate the QModelIndex without the column information const QModelIndex &properItemClickedIndex = mView->model()->index(itemClicked.row(), 0); AttachmentPart::Ptr part = mView->model()->data( properItemClickedIndex, MessageComposer::AttachmentModel::AttachmentPartRole).value(); // We can't edit encapsulated messages, but we can view them. if (part->isMessageOrMessageCollection()) { viewAttachment(part); } else { editAttachment(part); } } diff --git a/src/editor/kmcomposerwin.cpp b/src/editor/kmcomposerwin.cpp index 7d2396eca..d44196e96 100644 --- a/src/editor/kmcomposerwin.cpp +++ b/src/editor/kmcomposerwin.cpp @@ -1,3656 +1,3652 @@ /* * This file is part of KMail. * Copyright (c) 2011-2018 Laurent Montel * * Copyright (c) 2009 Constantin Berzan * * Based on KMail code by: * Copyright (c) 1997 Markus Wuebben * * 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 "kmcomposerwin.h" // KMail includes #include "attachment/attachmentcontroller.h" #include "attachment/attachmentview.h" #include "codec/codecaction.h" #include "custommimeheader.h" #include "editor/kmcomposereditorng.h" #include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h" #include "editor/plugininterface/kmailplugineditorinitmanagerinterface.h" #include "editor/plugininterface/kmailplugineditormanagerinterface.h" #include "editor/plugininterface/kmailplugineditorconverttextmanagerinterface.h" #include "editor/potentialphishingemail/potentialphishingemailjob.h" #include "editor/potentialphishingemail/potentialphishingemailwarning.h" #include "editor/warningwidgets/incorrectidentityfolderwarning.h" #include "editor/widgets/snippetwidget.h" #include "job/addressvalidationjob.h" #include "job/createnewcontactjob.h" #include "job/saveasfilejob.h" #include "job/savedraftjob.h" #include "job/dndfromarkjob.h" #include "kconfigwidgets_version.h" #include "kmail_debug.h" #include "kmcommands.h" #include "kmcomposercreatenewcomposerjob.h" #include "kmcomposerglobalaction.h" #include "kmcomposerupdatetemplatejob.h" #include "kmkernel.h" #include "kmmainwidget.h" #include "kmmainwin.h" #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff... #include "settings/kmailsettings.h" #include "templatesconfiguration_kfg.h" #include "util.h" #include "validatesendmailshortcut.h" #include "warningwidgets/attachmentmissingwarning.h" #include "warningwidgets/externaleditorwarning.h" #include "widgets/cryptostateindicatorwidget.h" #include "widgets/kactionmenutransport.h" #include "widgets/statusbarlabeltoggledstate.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_CURSOR #include #endif #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 #include #include #include #include #include #include #include #include #include #include #include #include // KDE Frameworks 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 // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // GPGME #include #include using Sonnet::DictionaryComboBox; using MailTransport::TransportManager; using MailTransport::Transport; Q_DECLARE_METATYPE(MessageComposer::Recipient::Ptr) KMail::Composer *KMail::makeComposer(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint identity, const QString &textSelection, const QString &customTemplate) { return KMComposerWin::create(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate); } KMail::Composer *KMComposerWin::create(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint identity, const QString &textSelection, const QString &customTemplate) { return new KMComposerWin(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate); } int KMComposerWin::s_composerNumber = 0; KMComposerWin::KMComposerWin(const KMime::Message::Ptr &aMsg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint id, const QString &textSelection, const QString &customTemplate) : KMail::Composer(QStringLiteral("kmail-composer#")) , mTextSelection(textSelection) , mCustomTemplate(customTemplate) , mFolder(Akonadi::Collection(-1)) , mId(id) , mContext(context) { mGlobalAction = new KMComposerGlobalAction(this, this); mComposerBase = new MessageComposer::ComposerViewBase(this, this); mComposerBase->setIdentityManager(kmkernel->identityManager()); mPluginEditorManagerInterface = new KMailPluginEditorManagerInterface(this); connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::message, this, &KMComposerWin::slotMessage); mPluginEditorCheckBeforeSendManagerInterface = new KMailPluginEditorCheckBeforeSendManagerInterface(this); mPluginEditorInitManagerInterface = new KMailPluginEditorInitManagerInterface(this); mPluginEditorConvertTextManagerInterface = new KMailPluginEditorConvertTextManagerInterface(this); connect(mComposerBase, &MessageComposer::ComposerViewBase::disableHtml, this, &KMComposerWin::disableHtml); connect(mComposerBase, &MessageComposer::ComposerViewBase::enableHtml, this, &KMComposerWin::enableHtml); connect(mComposerBase, &MessageComposer::ComposerViewBase::failed, this, &KMComposerWin::slotSendFailed); connect(mComposerBase, &MessageComposer::ComposerViewBase::sentSuccessfully, this, &KMComposerWin::slotSendSuccessful); connect(mComposerBase, &MessageComposer::ComposerViewBase::modified, this, &KMComposerWin::setModified); (void)new MailcomposerAdaptor(this); mdbusObjectPath = QLatin1String("/Composer_") + QString::number(++s_composerNumber); QDBusConnection::sessionBus().registerObject(mdbusObjectPath, this); MessageComposer::SignatureController *sigController = new MessageComposer::SignatureController(this); connect(sigController, &MessageComposer::SignatureController::enableHtml, this, &KMComposerWin::enableHtml); mComposerBase->setSignatureController(sigController); if (!kmkernel->xmlGuiInstanceName().isEmpty()) { setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2")); } mMainWidget = new QWidget(this); // splitter between the headers area and the actual editor mHeadersToEditorSplitter = new QSplitter(Qt::Vertical, mMainWidget); mHeadersToEditorSplitter->setObjectName(QStringLiteral("mHeadersToEditorSplitter")); mHeadersToEditorSplitter->setChildrenCollapsible(false); mHeadersArea = new QWidget(mHeadersToEditorSplitter); mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding); mHeadersToEditorSplitter->addWidget(mHeadersArea); const QList defaultSizes{ 0 }; mHeadersToEditorSplitter->setSizes(defaultSizes); QVBoxLayout *v = new QVBoxLayout(mMainWidget); v->setMargin(0); v->addWidget(mHeadersToEditorSplitter); KIdentityManagement::IdentityCombo *identity = new KIdentityManagement::IdentityCombo(kmkernel->identityManager(), mHeadersArea); identity->setCurrentIdentity(mId); connect(identity, &KIdentityManagement::IdentityCombo::identityDeleted, this, &KMComposerWin::slotIdentityDeleted); connect(identity, &KIdentityManagement::IdentityCombo::invalidIdentity, this, &KMComposerWin::slotInvalidIdentity); mComposerBase->setIdentityCombo(identity); sigController->setIdentityCombo(identity); sigController->suspend(); // we have to do identity change tracking ourselves due to the template code Sonnet::DictionaryComboBox *dictionaryCombo = new DictionaryComboBox(mHeadersArea); dictionaryCombo->setToolTip(i18n("Select the dictionary to use when spell-checking this message")); mComposerBase->setDictionary(dictionaryCombo); mFccFolder = new MailCommon::FolderRequester(mHeadersArea); mFccFolder->setNotAllowToCreateNewFolder(true); mFccFolder->setMustBeReadWrite(true); mFccFolder->setToolTip(i18n("Select the sent-mail folder where a copy of this message will be saved")); connect(mFccFolder, &MailCommon::FolderRequester::folderChanged, this, &KMComposerWin::slotFccFolderChanged); connect(mFccFolder, &MailCommon::FolderRequester::invalidFolder, this, &KMComposerWin::slotFccIsInvalid); MailTransport::TransportComboBox *transport = new MailTransport::TransportComboBox(mHeadersArea); transport->setToolTip(i18n("Select the outgoing account to use for sending this message")); mComposerBase->setTransportCombo(transport); connect(transport, QOverload::of(&MailTransport::TransportComboBox::activated), this, &KMComposerWin::slotTransportChanged); connect(transport, &MailTransport::TransportComboBox::transportRemoved, this, &KMComposerWin::slotTransportRemoved); mEdtFrom = new MessageComposer::ComposerLineEdit(false, mHeadersArea); mEdtFrom->setObjectName(QStringLiteral("fromLine")); mEdtFrom->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); mEdtFrom->setToolTip(i18n("Set the \"From:\" email address for this message")); mEdtReplyTo = new MessageComposer::ComposerLineEdit(true, mHeadersArea); mEdtReplyTo->setObjectName(QStringLiteral("replyToLine")); mEdtReplyTo->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); mEdtReplyTo->setToolTip(i18n("Set the \"Reply-To:\" email address for this message")); connect(mEdtReplyTo, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); MessageComposer::RecipientsEditor *recipientsEditor = new MessageComposer::RecipientsEditor(mHeadersArea); recipientsEditor->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); connect(recipientsEditor, &MessageComposer::RecipientsEditor::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); connect(recipientsEditor, &MessageComposer::RecipientsEditor::sizeHintChanged, this, &KMComposerWin::recipientEditorSizeHintChanged); connect(recipientsEditor, &MessageComposer::RecipientsEditor::lineAdded, this, &KMComposerWin::slotRecipientEditorLineAdded); mComposerBase->setRecipientsEditor(recipientsEditor); mEdtSubject = new PimCommon::LineEditWithAutoCorrection(mHeadersArea, QStringLiteral("kmail2rc")); mEdtSubject->setActivateLanguageMenu(false); mEdtSubject->setToolTip(i18n("Set a subject for this message")); mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection()); mLblIdentity = new QLabel(i18n("&Identity:"), mHeadersArea); mDictionaryLabel = new QLabel(i18n("&Dictionary:"), mHeadersArea); mLblFcc = new QLabel(i18n("&Sent-Mail folder:"), mHeadersArea); mLblTransport = new QLabel(i18n("&Mail transport:"), mHeadersArea); mLblFrom = new QLabel(i18nc("sender address field", "&From:"), mHeadersArea); mLblReplyTo = new QLabel(i18n("&Reply to:"), mHeadersArea); mLblSubject = new QLabel(i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea); mShowHeaders = KMailSettings::self()->headers(); mDone = false; mGrid = nullptr; mFixedFontAction = nullptr; // the attachment view is separated from the editor by a splitter mSplitter = new QSplitter(Qt::Vertical, mMainWidget); mSplitter->setObjectName(QStringLiteral("mSplitter")); mSplitter->setChildrenCollapsible(false); mSnippetSplitter = new QSplitter(Qt::Horizontal, mSplitter); mSnippetSplitter->setObjectName(QStringLiteral("mSnippetSplitter")); mSplitter->addWidget(mSnippetSplitter); QWidget *editorAndCryptoStateIndicators = new QWidget(mSplitter); mCryptoStateIndicatorWidget = new CryptoStateIndicatorWidget; mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator()); QVBoxLayout *vbox = new QVBoxLayout(editorAndCryptoStateIndicators); vbox->setMargin(0); mPotentialPhishingEmailWarning = new PotentialPhishingEmailWarning(this); connect(mPotentialPhishingEmailWarning, &PotentialPhishingEmailWarning::sendNow, this, &KMComposerWin::slotCheckSendNowStep2); vbox->addWidget(mPotentialPhishingEmailWarning); mAttachmentMissing = new AttachmentMissingWarning(this); connect(mAttachmentMissing, &AttachmentMissingWarning::attachMissingFile, this, &KMComposerWin::slotAttachMissingFile); connect(mAttachmentMissing, &AttachmentMissingWarning::explicitClosedMissingAttachment, this, &KMComposerWin::slotExplicitClosedMissingAttachment); vbox->addWidget(mAttachmentMissing); KMComposerEditorNg *composerEditorNg = new KMComposerEditorNg(this, mCryptoStateIndicatorWidget); mRichTextEditorwidget = new KPIMTextEdit::RichTextEditorWidget(composerEditorNg, mCryptoStateIndicatorWidget); //Don't use new connect api here. It crashs connect(composerEditorNg, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged())); connect(composerEditorNg, &KMComposerEditorNg::selectionChanged, this, &KMComposerWin::slotSelectionChanged); //connect(editor, &KMComposerEditor::textChanged, this, &KMComposeWin::slotEditorTextChanged); mComposerBase->setEditor(composerEditorNg); mIncorrectIdentityFolderWarning = new IncorrectIdentityFolderWarning(this); vbox->addWidget(mIncorrectIdentityFolderWarning); vbox->addWidget(mCryptoStateIndicatorWidget); vbox->addWidget(mRichTextEditorwidget); mSnippetSplitter->insertWidget(0, editorAndCryptoStateIndicators); mSnippetSplitter->setOpaqueResize(true); sigController->setEditor(composerEditorNg); mHeadersToEditorSplitter->addWidget(mSplitter); composerEditorNg->setAcceptDrops(true); connect(sigController, &MessageComposer::SignatureController::signatureAdded, mComposerBase->editor()->externalComposer(), &KPIMTextEdit::RichTextExternalComposer::startExternalEditor); connect(dictionaryCombo, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KMComposerWin::slotSpellCheckingLanguage); connect(composerEditorNg, &KMComposerEditorNg::languageChanged, this, &KMComposerWin::slotDictionaryLanguageChanged); connect(composerEditorNg, &KMComposerEditorNg::spellCheckStatus, this, &KMComposerWin::slotSpellCheckingStatus); connect(composerEditorNg, &KMComposerEditorNg::insertModeChanged, this, &KMComposerWin::slotOverwriteModeChanged); connect(composerEditorNg, &KMComposerEditorNg::spellCheckingFinished, this, &KMComposerWin::slotDelayedCheckSendNow); mSnippetWidget = new SnippetWidget(composerEditorNg, actionCollection(), mSnippetSplitter); mSnippetWidget->setVisible(KMailSettings::self()->showSnippetManager()); mSnippetSplitter->addWidget(mSnippetWidget); mSnippetSplitter->setCollapsible(0, false); mSnippetSplitterCollapser = new KSplitterCollapserButton(mSnippetWidget, mSnippetSplitter); mSnippetSplitterCollapser->setVisible(KMailSettings::self()->showSnippetManager()); mSplitter->setOpaqueResize(true); setWindowTitle(i18n("Composer")); setMinimumSize(200, 200); mCustomToolsWidget = new PimCommon::CustomToolsWidgetNg(actionCollection(), this); mSplitter->addWidget(mCustomToolsWidget); connect(mCustomToolsWidget, &PimCommon::CustomToolsWidgetNg::insertText, this, &KMComposerWin::slotInsertShortUrl); MessageComposer::AttachmentModel *attachmentModel = new MessageComposer::AttachmentModel(this); KMail::AttachmentView *attachmentView = new KMail::AttachmentView(attachmentModel, mSplitter); attachmentView->hideIfEmpty(); connect(attachmentView, &KMail::AttachmentView::modified, this, &KMComposerWin::setModified); KMail::AttachmentController *attachmentController = new KMail::AttachmentController(attachmentModel, attachmentView, this); mComposerBase->setAttachmentModel(attachmentModel); mComposerBase->setAttachmentController(attachmentController); if (KMailSettings::self()->showForgottenAttachmentWarning()) { mVerifyMissingAttachment = new QTimer(this); mVerifyMissingAttachment->setSingleShot(true); mVerifyMissingAttachment->setInterval(1000 * 5); connect(mVerifyMissingAttachment, &QTimer::timeout, this, &KMComposerWin::slotVerifyMissingAttachmentTimeout); } connect(attachmentController, &KMail::AttachmentController::fileAttached, mAttachmentMissing, &AttachmentMissingWarning::slotFileAttached); mExternalEditorWarning = new ExternalEditorWarning(this); v->addWidget(mExternalEditorWarning); mPluginEditorManagerInterface->setParentWidget(this); mPluginEditorManagerInterface->setRichTextEditor(mRichTextEditorwidget->editor()); mPluginEditorManagerInterface->setActionCollection(actionCollection()); mPluginEditorCheckBeforeSendManagerInterface->setParentWidget(this); mPluginEditorInitManagerInterface->setParent(this); mPluginEditorInitManagerInterface->setRichTextEditor(composerEditorNg); mPluginEditorConvertTextManagerInterface->setParentWidget(this); mPluginEditorConvertTextManagerInterface->setActionCollection(actionCollection()); mPluginEditorConvertTextManagerInterface->setRichTextEditor(composerEditorNg); setupStatusBar(attachmentView->widget()); setupActions(); setupEditor(); rethinkFields(); readConfig(); updateSignatureAndEncryptionStateIndicators(); applyMainWindowSettings(KMKernel::self()->config()->group("Composer")); connect(mEdtSubject, &PimCommon::LineEditWithAutoCorrection::textChanged, this, &KMComposerWin::slotUpdateWindowTitle); mIdentityConnection = connect(identity, &KIdentityManagement::IdentityCombo::identityChanged, [this](uint val) { slotIdentityChanged(val); }); connect(kmkernel->identityManager(), QOverload::of(&KIdentityManagement::IdentityManager::changed), this, [this](uint val) { if (mComposerBase->identityCombo()->currentIdentity() == val) { slotIdentityChanged(val); } }); connect(mEdtFrom, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::collectionRemoved, this, &KMComposerWin::slotFolderRemoved); connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::slotConfigChanged); mMainWidget->resize(800, 600); setCentralWidget(mMainWidget); if (KMailSettings::self()->useHtmlMarkup()) { enableHtml(); } else { disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm); } if (KMailSettings::self()->useExternalEditor()) { composerEditorNg->setUseExternalEditor(true); composerEditorNg->setExternalEditorPath(KMailSettings::self()->externalEditor()); } const QList lstLines = recipientsEditor->lines(); for (KPIM::MultiplyingLine *line : lstLines) { slotRecipientEditorLineAdded(line); } if (aMsg) { setMessage(aMsg, lastSignState, lastEncryptState); } mComposerBase->recipientsEditor()->setFocus(); composerEditorNg->composerActions()->updateActionStates(); // set toolbar buttons to correct values mDone = true; mDummyComposer = new MessageComposer::Composer(this); mDummyComposer->globalPart()->setParentWidgetForGui(this); KConfigGroup grp(KMKernel::self()->config()->group("Composer")); setAutoSaveSettings(grp, true); } KMComposerWin::~KMComposerWin() { // When we have a collection set, store the message back to that collection. // Note that when we save the message or sent it, mFolder is set back to 0. // So this for example kicks in when opening a draft and then closing the window. if (mFolder.isValid() && mMsg && isModified()) { SaveDraftJob *saveDraftJob = new SaveDraftJob(mMsg, mFolder); saveDraftJob->start(); } delete mComposerBase; } void KMComposerWin::slotSpellCheckingLanguage(const QString &language) { mComposerBase->editor()->setSpellCheckingLanguage(language); mEdtSubject->setSpellCheckingLanguage(language); } QString KMComposerWin::dbusObjectPath() const { return mdbusObjectPath; } void KMComposerWin::slotEditorTextChanged() { const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty(); mFindText->setEnabled(textIsNotEmpty); mFindNextText->setEnabled(textIsNotEmpty); mReplaceText->setEnabled(textIsNotEmpty); mSelectAll->setEnabled(textIsNotEmpty); if (mVerifyMissingAttachment && !mVerifyMissingAttachment->isActive()) { mVerifyMissingAttachment->start(); } } void KMComposerWin::send(int how) { switch (how) { case 1: slotSendNow(); break; default: case 0: // TODO: find out, what the default send method is and send it this way case 2: slotSendLater(); break; } } void KMComposerWin::addAttachmentsAndSend(const QList &urls, const QString &comment, int how) { const int nbUrl = urls.count(); for (int i = 0; i < nbUrl; ++i) { mComposerBase->addAttachment(urls.at(i), comment, true); } send(how); } void KMComposerWin::addAttachment(const QUrl &url, const QString &comment) { mComposerBase->addAttachment(url, comment, false); } void KMComposerWin::addAttachment(const QString &name, KMime::Headers::contentEncoding cte, const QString &charset, const QByteArray &data, const QByteArray &mimeType) { Q_UNUSED(cte); mComposerBase->addAttachment(name, name, charset, data, mimeType); } void KMComposerWin::readConfig(bool reload /* = false */) { mEdtFrom->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode()); mComposerBase->recipientsEditor()->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode()); mEdtReplyTo->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode()); if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) { mBodyFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); mFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); } else { mBodyFont = KMailSettings::self()->composerFont(); mFixedFont = MessageViewer::MessageViewerSettings::self()->fixedFont(); } slotUpdateFont(); mEdtFrom->setFont(mBodyFont); mEdtReplyTo->setFont(mBodyFont); mEdtSubject->setFont(mBodyFont); if (!reload) { QSize siz = KMailSettings::self()->composerSize(); if (siz.width() < 200) { siz.setWidth(200); } if (siz.height() < 200) { siz.setHeight(200); } resize(siz); if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) { mSnippetSplitter->setSizes(KMailSettings::self()->snippetSplitterPosition()); } else { QList defaults; defaults << (int)(width() * 0.8) << (int)(width() * 0.2); mSnippetSplitter->setSizes(defaults); } } mComposerBase->identityCombo()->setCurrentIdentity(mId); qCDebug(KMAIL_LOG) << mComposerBase->identityCombo()->currentIdentityName(); const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(mId); mComposerBase->setAutoSaveInterval(KMailSettings::self()->autosaveInterval() * 1000 * 60); mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); QString fccName; if (!ident.fcc().isEmpty()) { fccName = ident.fcc(); } setFcc(fccName); } void KMComposerWin::writeConfig(void) { KMailSettings::self()->setHeaders(mShowHeaders); KMailSettings::self()->setCurrentTransport(mComposerBase->transportComboBox()->currentText()); KMailSettings::self()->setPreviousIdentity(mComposerBase->identityCombo()->currentIdentity()); KMailSettings::self()->setPreviousFcc(QString::number(mFccFolder->collection().id())); KMailSettings::self()->setPreviousDictionary(mComposerBase->dictionary()->currentDictionaryName()); KMailSettings::self()->setAutoSpellChecking(mAutoSpellCheckingAction->isChecked()); MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mFixedFontAction->isChecked()); if (!mForceDisableHtml) { KMailSettings::self()->setUseHtmlMarkup(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); } KMailSettings::self()->setComposerSize(size()); KMailSettings::self()->setShowSnippetManager(mSnippetAction->isChecked()); if (mSnippetAction->isChecked()) { KMailSettings::setSnippetSplitterPosition(mSnippetSplitter->sizes()); } // make sure config changes are written to disk, cf. bug 127538 KMKernel::self()->slotSyncConfig(); } MessageComposer::Composer *KMComposerWin::createSimpleComposer() { QList< QByteArray > charsets = mCodecAction->mimeCharsets(); if (!mOriginalPreferredCharset.isEmpty()) { charsets.insert(0, mOriginalPreferredCharset); } mComposerBase->setFrom(from()); mComposerBase->setReplyTo(replyTo()); mComposerBase->setSubject(subject()); mComposerBase->setCharsets(charsets); return mComposerBase->createSimpleComposer(); } bool KMComposerWin::canSignEncryptAttachments() const { return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat; } void KMComposerWin::slotUpdateView() { if (!mDone) { return; // otherwise called from rethinkFields during the construction // which is not the intended behavior } //This sucks awfully, but no, I cannot get an activated(int id) from // actionContainer() KToggleAction *act = ::qobject_cast(sender()); if (!act) { return; } int id; if (act == mAllFieldsAction) { id = 0; } else if (act == mIdentityAction) { id = HDR_IDENTITY; } else if (act == mTransportAction) { id = HDR_TRANSPORT; } else if (act == mFromAction) { id = HDR_FROM; } else if (act == mReplyToAction) { id = HDR_REPLY_TO; } else if (act == mSubjectAction) { id = HDR_SUBJECT; } else if (act == mFccAction) { id = HDR_FCC; } else if (act == mDictionaryAction) { id = HDR_DICTIONARY; } else { qCDebug(KMAIL_LOG) << "Something is wrong (Oh, yeah?)"; return; } bool forceAllHeaders = false; // sanders There's a bug here this logic doesn't work if no // fields are shown and then show all fields is selected. // Instead of all fields being shown none are. if (!act->isChecked()) { // hide header if (id > 0) { mShowHeaders = mShowHeaders & ~id; } else { mShowHeaders = std::abs(mShowHeaders); } } else { // show header if (id > 0) { mShowHeaders |= id; } else { mShowHeaders = -std::abs(mShowHeaders); if (mShowHeaders == 0) { forceAllHeaders = true; } } } rethinkFields(true, forceAllHeaders); } int KMComposerWin::calcColumnWidth(int which, long allShowing, int width) const { if ((allShowing & which) == 0) { return width; } QLabel *w = nullptr; if (which == HDR_IDENTITY) { w = mLblIdentity; } else if (which == HDR_DICTIONARY) { w = mDictionaryLabel; } else if (which == HDR_FCC) { w = mLblFcc; } else if (which == HDR_TRANSPORT) { w = mLblTransport; } else if (which == HDR_FROM) { w = mLblFrom; } else if (which == HDR_REPLY_TO) { w = mLblReplyTo; } else if (which == HDR_SUBJECT) { w = mLblSubject; } else { return width; } w->setBuddy(mComposerBase->editor()); // set dummy so we don't calculate width of '&' for this label. w->adjustSize(); w->show(); return qMax(width, w->sizeHint().width()); } void KMComposerWin::rethinkFields(bool fromSlot, bool forceAllHeaders) { //This sucks even more but again no ids. sorry (sven) int mask, row; long showHeaders; if ((mShowHeaders < 0) || forceAllHeaders) { showHeaders = HDR_ALL; } else { showHeaders = mShowHeaders; } for (mask = 1, mNumHeaders = 0; mask <= showHeaders; mask <<= 1) { if ((showHeaders & mask) != 0) { ++mNumHeaders; } } delete mGrid; mGrid = new QGridLayout(mHeadersArea); mGrid->setColumnStretch(0, 1); mGrid->setColumnStretch(1, 100); mGrid->setRowStretch(mNumHeaders + 1, 100); row = 0; mLabelWidth = mComposerBase->recipientsEditor()->setFirstColumnWidth(0) + 2; if (std::abs(showHeaders)&HDR_IDENTITY) { mLabelWidth = calcColumnWidth(HDR_IDENTITY, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_DICTIONARY) { mLabelWidth = calcColumnWidth(HDR_DICTIONARY, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_FCC) { mLabelWidth = calcColumnWidth(HDR_FCC, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_TRANSPORT) { mLabelWidth = calcColumnWidth(HDR_TRANSPORT, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_FROM) { mLabelWidth = calcColumnWidth(HDR_FROM, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_REPLY_TO) { mLabelWidth = calcColumnWidth(HDR_REPLY_TO, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_SUBJECT) { mLabelWidth = calcColumnWidth(HDR_SUBJECT, showHeaders, mLabelWidth); } if (!fromSlot) { mAllFieldsAction->setChecked(showHeaders == HDR_ALL); } if (!fromSlot) { mIdentityAction->setChecked(std::abs(mShowHeaders)&HDR_IDENTITY); } rethinkHeaderLine(showHeaders, HDR_IDENTITY, row, mLblIdentity, mComposerBase->identityCombo()); if (!fromSlot) { mDictionaryAction->setChecked(std::abs(mShowHeaders)&HDR_DICTIONARY); } rethinkHeaderLine(showHeaders, HDR_DICTIONARY, row, mDictionaryLabel, mComposerBase->dictionary()); if (!fromSlot) { mFccAction->setChecked(std::abs(mShowHeaders)&HDR_FCC); } rethinkHeaderLine(showHeaders, HDR_FCC, row, mLblFcc, mFccFolder); if (!fromSlot) { mTransportAction->setChecked(std::abs(mShowHeaders)&HDR_TRANSPORT); } rethinkHeaderLine(showHeaders, HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox()); if (!fromSlot) { mFromAction->setChecked(std::abs(mShowHeaders)&HDR_FROM); } rethinkHeaderLine(showHeaders, HDR_FROM, row, mLblFrom, mEdtFrom); QWidget *prevFocus = mEdtFrom; if (!fromSlot) { mReplyToAction->setChecked(std::abs(mShowHeaders)&HDR_REPLY_TO); } rethinkHeaderLine(showHeaders, HDR_REPLY_TO, row, mLblReplyTo, mEdtReplyTo); if (showHeaders & HDR_REPLY_TO) { (void)connectFocusMoving(prevFocus, mEdtReplyTo); } mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2); ++row; if (showHeaders & HDR_REPLY_TO) { connect(mEdtReplyTo, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop); connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtReplyTo, QOverload<>::of(&QWidget::setFocus)); } else { connect(mEdtFrom, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop); connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtFrom, QOverload<>::of(&QWidget::setFocus)); } connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusDown, mEdtSubject, QOverload<>::of(&QWidget::setFocus)); connect(mEdtSubject, &PimCommon::SpellCheckLineEdit::focusUp, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusBottom); prevFocus = mComposerBase->recipientsEditor(); if (!fromSlot) { mSubjectAction->setChecked(std::abs(mShowHeaders)&HDR_SUBJECT); } rethinkHeaderLine(showHeaders, HDR_SUBJECT, row, mLblSubject, mEdtSubject); connectFocusMoving(mEdtSubject, mComposerBase->editor()); assert(row <= mNumHeaders + 1); mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height()); mIdentityAction->setEnabled(!mAllFieldsAction->isChecked()); mDictionaryAction->setEnabled(!mAllFieldsAction->isChecked()); mTransportAction->setEnabled(!mAllFieldsAction->isChecked()); mFromAction->setEnabled(!mAllFieldsAction->isChecked()); if (mReplyToAction) { mReplyToAction->setEnabled(!mAllFieldsAction->isChecked()); } mFccAction->setEnabled(!mAllFieldsAction->isChecked()); mSubjectAction->setEnabled(!mAllFieldsAction->isChecked()); mComposerBase->recipientsEditor()->setFirstColumnWidth(mLabelWidth); } QWidget *KMComposerWin::connectFocusMoving(QWidget *prev, QWidget *next) { connect(prev, SIGNAL(focusDown()), next, SLOT(setFocus())); connect(next, SIGNAL(focusUp()), prev, SLOT(setFocus())); return next; } void KMComposerWin::rethinkHeaderLine(int aValue, int aMask, int &aRow, QLabel *aLbl, QWidget *aCbx) { if (aValue & aMask) { aLbl->setBuddy(aCbx); aLbl->setFixedWidth(mLabelWidth); mGrid->addWidget(aLbl, aRow, 0); mGrid->addWidget(aCbx, aRow, 1); aCbx->show(); aLbl->show(); aRow++; } else { aLbl->hide(); aCbx->hide(); } } void KMComposerWin::slotUpdateComposer(const KIdentityManagement::Identity &ident, const KMime::Message::Ptr &msg, uint uoid, uint uoldId, bool wasModified) { mComposerBase->updateTemplate(msg); updateSignature(uoid, uoldId); updateComposerAfterIdentityChanged(ident, uoid, wasModified); } void KMComposerWin::applyTemplate(uint uoid, uint uOldId, const KIdentityManagement::Identity &ident, bool wasModified) { TemplateParser::TemplateParserJob::Mode mode; switch (mContext) { case New: mode = TemplateParser::TemplateParserJob::NewMessage; break; case Reply: mode = TemplateParser::TemplateParserJob::Reply; break; case ReplyToAll: mode = TemplateParser::TemplateParserJob::ReplyAll; break; case Forward: mode = TemplateParser::TemplateParserJob::Forward; break; case NoTemplate: updateComposerAfterIdentityChanged(ident, uoid, wasModified); return; } KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Templates"); header->fromUnicodeString(ident.templates(), "utf-8"); mMsg->setHeader(header); if (mode == TemplateParser::TemplateParserJob::NewMessage) { KMComposerUpdateTemplateJob *job = new KMComposerUpdateTemplateJob; connect(job, &KMComposerUpdateTemplateJob::updateComposer, this, &KMComposerWin::slotUpdateComposer); job->setMsg(mMsg); job->setCustomTemplate(mCustomTemplate); job->setTextSelection(mTextSelection); job->setWasModified(wasModified); job->setUoldId(uOldId); job->setUoid(uoid); job->setIdent(ident); job->setCollection(mCollectionForNewMessage); job->start(); } else { if (auto hrd = mMsg->headerByType("X-KMail-Link-Message")) { Akonadi::Item::List items; const QStringList serNums = hrd->asUnicodeString().split(QLatin1Char(',')); items.reserve(serNums.count()); for (const QString &serNumStr : serNums) { items << Akonadi::Item(serNumStr.toLongLong()); } Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(items, this); job->fetchScope().fetchFullPayload(true); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); job->setProperty("mode", static_cast(mode)); job->setProperty("uoid", uoid); job->setProperty("uOldid", uOldId); connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDelayedApplyTemplate); } updateComposerAfterIdentityChanged(ident, uoid, wasModified); } } void KMComposerWin::slotDelayedApplyTemplate(KJob *job) { const Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); const Akonadi::Item::List items = fetchJob->items(); const TemplateParser::TemplateParserJob::Mode mode = static_cast(fetchJob->property("mode").toInt()); const uint uoid = fetchJob->property("uoid").toUInt(); const uint uOldId = fetchJob->property("uOldid").toUInt(); #if 0 //FIXME template TemplateParser::TemplateParser parser(mMsg, mode); parser.setSelection(mTextSelection); parser.setAllowDecryption(true); parser.setWordWrap(MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth()); parser.setIdentityManager(KMKernel::self()->identityManager()); for (const Akonadi::Item &item : items) { if (!mCustomTemplate.isEmpty()) { parser.process(mCustomTemplate, MessageCore::Util::message(item)); } else { parser.processWithIdentity(uoid, MessageCore::Util::message(item)); } } #else mComposerBase->updateTemplate(mMsg); updateSignature(uoid, uOldId); qCWarning(KMAIL_LOG) << " void KMComposerWin::slotDelayedApplyTemplate(KJob *job) is not implemented after removing qtwebkit"; #endif } void KMComposerWin::updateSignature(uint uoid, uint uOldId) { const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid); const KIdentityManagement::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid(uOldId); mComposerBase->identityChanged(ident, oldIdentity, true); } void KMComposerWin::setCollectionForNewMessage(const Akonadi::Collection &folder) { mCollectionForNewMessage = folder; } void KMComposerWin::setQuotePrefix(uint uoid) { QString quotePrefix; if (auto hrd = mMsg->headerByType("X-KMail-QuotePrefix")) { quotePrefix = hrd->asUnicodeString(); } if (quotePrefix.isEmpty()) { // no quote prefix header, set quote prefix according in identity // TODO port templates to ComposerViewBase if (mCustomTemplate.isEmpty()) { const KIdentityManagement::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault(uoid); // Get quote prefix from template // ( custom templates don't specify custom quotes prefixes ) TemplateParser::Templates quoteTemplate( TemplateParser::TemplatesConfiguration::configIdString(identity.uoid())); quotePrefix = quoteTemplate.quoteString(); } } mComposerBase->editor()->setQuotePrefixName(MessageCore::StringUtil::formatQuotePrefix(quotePrefix, mMsg->from()->displayString())); } void KMComposerWin::setupActions(void) { KActionMenuTransport *actActionNowMenu = nullptr; KActionMenuTransport *actActionLaterMenu = nullptr; if (MessageComposer::MessageComposerSettings::self()->sendImmediate()) { //default = send now, alternative = queue QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this); actionCollection()->addAction(QStringLiteral("send_default"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Return)); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNowByShortcut); actActionNowMenu = new KActionMenuTransport(this); actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); actActionNowMenu->setText(i18n("&Send Mail Via")); actActionNowMenu->setIconText(i18n("Send")); actionCollection()->addAction(QStringLiteral("send_default_via"), actActionNowMenu); action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this); actionCollection()->addAction(QStringLiteral("send_alternative"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater); actActionLaterMenu = new KActionMenuTransport(this); actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue"))); actActionLaterMenu->setText(i18n("Send &Later Via")); actActionLaterMenu->setIconText(i18nc("Queue the message for sending at a later date", "Queue")); actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionLaterMenu); } else { //default = queue, alternative = send now QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this); actionCollection()->addAction(QStringLiteral("send_default"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Return)); actActionLaterMenu = new KActionMenuTransport(this); actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue"))); actActionLaterMenu->setText(i18n("Send &Later Via")); actionCollection()->addAction(QStringLiteral("send_default_via"), actActionLaterMenu); action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this); actionCollection()->addAction(QStringLiteral("send_alternative"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNow); actActionNowMenu = new KActionMenuTransport(this); actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); actActionNowMenu->setText(i18n("&Send Mail Via")); actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionNowMenu); } connect(actActionNowMenu, &QAction::triggered, this, &KMComposerWin::slotSendNow); connect(actActionLaterMenu, &QAction::triggered, this, &KMComposerWin::slotSendLater); connect(actActionNowMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendNowVia); connect(actActionLaterMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendLaterVia); QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this); actionCollection()->addAction(QStringLiteral("save_in_drafts"), action); KMail::Util::addQActionHelpText(action, i18n("Save email in Draft folder")); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_S)); connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveDraft); action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Template"), this); KMail::Util::addQActionHelpText(action, i18n("Save email in Template folder")); actionCollection()->addAction(QStringLiteral("save_in_templates"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveTemplate); action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this); KMail::Util::addQActionHelpText(action, i18n("Save email as text or html file")); actionCollection()->addAction(QStringLiteral("save_as_file"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveAsFile); action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this); actionCollection()->addAction(QStringLiteral("insert_file"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotInsertFile); mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this); actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction); connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &KMComposerWin::slotInsertRecentFile); connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &KMComposerWin::slotRecentListFileClear); mRecentAction->loadEntries(KMKernel::self()->config()->group(QString())); action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this); KMail::Util::addQActionHelpText(action, i18n("Open Address Book")); actionCollection()->addAction(QStringLiteral("addressbook"), action); if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) { action->setEnabled(false); } else { connect(action, &QAction::triggered, this, &KMComposerWin::slotAddressBook); } action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Composer"), this); actionCollection()->addAction(QStringLiteral("new_composer"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotNewComposer); actionCollection()->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::New)); action = new QAction(i18n("Select &Recipients..."), this); actionCollection()->addAction(QStringLiteral("select_recipients"), action); connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::selectRecipients); action = new QAction(i18n("Save &Distribution List..."), this); actionCollection()->addAction(QStringLiteral("save_distribution_list"), action); connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::saveDistributionList); KStandardAction::print(this, &KMComposerWin::slotPrint, actionCollection()); KStandardAction::printPreview(this, &KMComposerWin::slotPrintPreview, actionCollection()); KStandardAction::close(this, &KMComposerWin::slotClose, actionCollection()); KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection()); KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection()); KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection()); KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection()); KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection()); mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection()); mFindText = KStandardAction::find(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFind, actionCollection()); mFindNextText = KStandardAction::findNext(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFindNext, actionCollection()); mReplaceText = KStandardAction::replace(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotReplace, actionCollection()); actionCollection()->addAction(KStandardAction::Spelling, QStringLiteral("spellcheck"), mComposerBase->editor(), SLOT(slotCheckSpelling())); action = new QAction(i18n("Paste as Attac&hment"), this); actionCollection()->addAction(QStringLiteral("paste_att"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotPasteAsAttachment); action = new QAction(i18n("Cl&ean Spaces"), this); actionCollection()->addAction(QStringLiteral("clean_spaces"), action); connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace); mFixedFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this); actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction); connect(mFixedFontAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateFont); mFixedFontAction->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont()); //these are checkable!!! mUrgentAction = new KToggleAction( i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this); actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction); mRequestMDNAction = new KToggleAction(i18n("&Request Disposition Notification"), this); actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction); mRequestMDNAction->setChecked(KMailSettings::self()->requestMDN()); mRequestDeliveryConfirmation = new KToggleAction(i18n("&Request Delivery Confirmation"), this); actionCollection()->addAction(QStringLiteral("options_request_delivery_confirmation"), mRequestDeliveryConfirmation); //TOOD mRequestDeliveryConfirmation->setChecked(KMailSettings::self()->requestMDN()); - //----- Message-Encoding Submenu mCodecAction = new CodecAction(CodecAction::ComposerMode, this); actionCollection()->addAction(QStringLiteral("charsets"), mCodecAction); mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this); actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction); mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap()); connect(mWordWrapAction, &KToggleAction::toggled, this, &KMComposerWin::slotWordWrapToggled); mSnippetAction = new KToggleAction(i18n("&Snippets"), this); actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction); connect(mSnippetAction, &KToggleAction::toggled, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged); mSnippetAction->setChecked(KMailSettings::self()->showSnippetManager()); mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this); actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction); const bool spellChecking = KMailSettings::self()->autoSpellChecking(); const bool useKmailEditor = !KMailSettings::self()->useExternalEditor(); const bool spellCheckingEnabled = useKmailEditor && spellChecking; mAutoSpellCheckingAction->setEnabled(useKmailEditor); mAutoSpellCheckingAction->setChecked(spellCheckingEnabled); slotAutoSpellCheckingToggled(spellCheckingEnabled); connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &KMComposerWin::slotAutoSpellCheckingToggled); connect(mComposerBase->editor(), &KPIMTextEdit::RichTextEditor::checkSpellingChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled); connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::textModeChanged, this, &KMComposerWin::slotTextModeChanged); connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorClosed, this, &KMComposerWin::slotExternalEditorClosed); connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorStarted, this, &KMComposerWin::slotExternalEditorStarted); //these are checkable!!! mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this); mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); mMarkupAction->setIconText(i18n("Rich Text")); mMarkupAction->setToolTip(i18n("Toggle rich text editing mode")); actionCollection()->addAction(QStringLiteral("html"), mMarkupAction); connect(mMarkupAction, &KToggleAction::triggered, this, &KMComposerWin::slotToggleMarkup); mAllFieldsAction = new KToggleAction(i18n("&All Fields"), this); actionCollection()->addAction(QStringLiteral("show_all_fields"), mAllFieldsAction); connect(mAllFieldsAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mIdentityAction = new KToggleAction(i18n("&Identity"), this); actionCollection()->addAction(QStringLiteral("show_identity"), mIdentityAction); connect(mIdentityAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this); actionCollection()->addAction(QStringLiteral("show_dictionary"), mDictionaryAction); connect(mDictionaryAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this); actionCollection()->addAction(QStringLiteral("show_fcc"), mFccAction); connect(mFccAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mTransportAction = new KToggleAction(i18n("&Mail Transport"), this); actionCollection()->addAction(QStringLiteral("show_transport"), mTransportAction); connect(mTransportAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mFromAction = new KToggleAction(i18n("&From"), this); actionCollection()->addAction(QStringLiteral("show_from"), mFromAction); connect(mFromAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mReplyToAction = new KToggleAction(i18n("&Reply To"), this); actionCollection()->addAction(QStringLiteral("show_reply_to"), mReplyToAction); connect(mReplyToAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mSubjectAction = new KToggleAction( i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this); actionCollection()->addAction(QStringLiteral("show_subject"), mSubjectAction); connect(mSubjectAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); //end of checkable mAppendSignature = new QAction(i18n("Append S&ignature"), this); actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature); connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature); mPrependSignature = new QAction(i18n("Pr&epend Signature"), this); actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature); connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature); mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this); actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition); connect(mInsertSignatureAtCursorPosition, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::insertSignatureAtCursor); mComposerBase->attachmentController()->createActions(); setStandardToolBarMenuEnabled(true); KStandardAction::keyBindings(this, &KMComposerWin::slotEditKeys, actionCollection()); KStandardAction::configureToolbars(this, &KMComposerWin::slotEditToolbars, actionCollection()); KStandardAction::preferences(kmkernel, &KMKernel::slotShowConfigurationDialog, actionCollection()); action = new QAction(i18n("&Spellchecker..."), this); action->setIconText(i18n("Spellchecker")); actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSpellcheckConfig); mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this); mEncryptAction->setIconText(i18n("Encrypt")); actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction); mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this); mSignAction->setIconText(i18n("Sign")); actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction); const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity()); // PENDING(marc): check the uses of this member and split it into // smime/openpgp and or enc/sign, if necessary: mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty(); mLastEncryptActionState = false; mLastSignActionState = ident.pgpAutoSign(); changeCryptoAction(); connect(mEncryptAction, &KToggleAction::triggered, this, &KMComposerWin::slotEncryptToggled); connect(mSignAction, &KToggleAction::triggered, this, &KMComposerWin::slotSignToggled); QStringList listCryptoFormat; listCryptoFormat.reserve(numCryptoMessageFormats); for (int i = 0; i < numCryptoMessageFormats; ++i) { listCryptoFormat.push_back(Kleo::cryptoMessageFormatToLabel(cryptoMessageFormats[i])); } mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this); actionCollection()->addAction(QStringLiteral("options_select_crypto"), mCryptoModuleAction); connect(mCryptoModuleAction, QOverload::of(&KSelectAction::triggered), this, &KMComposerWin::slotCryptoModuleSelected); mCryptoModuleAction->setToolTip(i18n("Select a cryptographic format for this message")); mCryptoModuleAction->setItems(listCryptoFormat); mComposerBase->editor()->createActions(actionCollection()); mFollowUpToggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("Create Follow Up Reminder..."), this); actionCollection()->addAction(QStringLiteral("follow_up_mail"), mFollowUpToggleAction); connect(mFollowUpToggleAction, &KToggleAction::triggered, this, &KMComposerWin::slotFollowUpMail); mFollowUpToggleAction->setEnabled(FollowUpReminder::FollowUpReminderUtil::followupReminderAgentEnabled()); mPluginEditorManagerInterface->initializePlugins(); mPluginEditorCheckBeforeSendManagerInterface->initializePlugins(); mPluginEditorInitManagerInterface->initializePlugins(); mPluginEditorConvertTextManagerInterface->initializePlugins(); mHideMenuBarAction = KStandardAction::showMenubar(this, &KMComposerWin::slotToggleMenubar, actionCollection()); mHideMenuBarAction->setChecked(KMailSettings::self()->composerShowMenuBar()); slotToggleMenubar(true); createGUI(QStringLiteral("kmcomposerui.rc")); initializePluginActions(); connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &KMComposerWin::htmlToolBarVisibilityChanged); // In Kontact, this entry would read "Configure Kontact", but bring // up KMail's config dialog. That's sensible, though, so fix the label. QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure")); if (configureAction) { configureAction->setText(i18n("Configure KMail...")); } } void KMComposerWin::slotToggleMenubar(bool dontShowWarning) { if (menuBar()) { if (mHideMenuBarAction->isChecked()) { menuBar()->show(); } else { if (!dontShowWarning) { const QString accel = mHideMenuBarAction->shortcut().toString(); KMessageBox::information(this, i18n("This will hide the menu bar completely." " You can show it again by typing %1.", accel), i18n("Hide menu bar"), QStringLiteral("HideMenuBarWarning")); } menuBar()->hide(); } KMailSettings::self()->setComposerShowMenuBar(mHideMenuBarAction->isChecked()); } } void KMComposerWin::initializePluginActions() { if (guiFactory()) { QHash > hashActions; QHashIterator > localEditorManagerActionsType(mPluginEditorManagerInterface->actionsType()); while (localEditorManagerActionsType.hasNext()) { localEditorManagerActionsType.next(); QList lst = localEditorManagerActionsType.value(); if (!lst.isEmpty()) { const QString actionlistname = QStringLiteral("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorManagerActionsType.key()); hashActions.insert(actionlistname, lst); } } QHashIterator > localEditorConvertTextManagerActionsType(mPluginEditorConvertTextManagerInterface->actionsType()); while (localEditorConvertTextManagerActionsType.hasNext()) { localEditorConvertTextManagerActionsType.next(); QList lst = localEditorConvertTextManagerActionsType.value(); if (!lst.isEmpty()) { const QString actionlistname = QStringLiteral("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorConvertTextManagerActionsType.key()); if (hashActions.contains(actionlistname)) { lst = hashActions.value(actionlistname) + lst; hashActions.remove(actionlistname); } hashActions.insert(actionlistname, lst); } } QHash >::const_iterator i = hashActions.constBegin(); while (i != hashActions.constEnd()) { Q_FOREACH (KXMLGUIClient *client, guiFactory()->clients()) { client->unplugActionList(i.key()); client->plugActionList(i.key(), i.value()); } ++i; } //Initialize statusbar const QList statusbarWidgetList = mPluginEditorManagerInterface->statusBarWidgetList(); for (int i = 0; i < statusbarWidgetList.count(); ++i) { statusBar()->addPermanentWidget(statusbarWidgetList.at(i), 0); } } } void KMComposerWin::changeCryptoAction() { const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity()); if (!QGpgME::openpgp() && !QGpgME::smime()) { // no crypto whatsoever mEncryptAction->setEnabled(false); setEncryption(false); mSignAction->setEnabled(false); setSigning(false); } else { const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty(); const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty(); setEncryption(false); setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign()); } } void KMComposerWin::setupStatusBar(QWidget *w) { statusBar()->addWidget(w); mStatusbarLabel = new QLabel(this); mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); statusBar()->addPermanentWidget(mStatusbarLabel); mCursorLineLabel = new QLabel(this); mCursorLineLabel->setTextFormat(Qt::PlainText); mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral(" "))); statusBar()->addPermanentWidget(mCursorLineLabel); mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral(" "))); mCursorColumnLabel->setTextFormat(Qt::PlainText); statusBar()->addPermanentWidget(mCursorColumnLabel); mStatusBarLabelToggledOverrideMode = new StatusBarLabelToggledState(this); mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS")); statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0); connect(mStatusBarLabelToggledOverrideMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotOverwriteModeWasChanged); mStatusBarLabelSpellCheckingChangeMode = new StatusBarLabelToggledState(this); mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off")); statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0); connect(mStatusBarLabelSpellCheckingChangeMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled); - } void KMComposerWin::setupEditor(void) { QFontMetrics fm(mBodyFont); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) mComposerBase->editor()->setTabStopDistance(fm.boundingRect(QLatin1Char(' ')).width() * 8); #else mComposerBase->editor()->setTabStopWidth(fm.boundingRect(QLatin1Char(' ')).width() * 8); #endif slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap()); // Font setup slotUpdateFont(); connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &KMComposerWin::slotCursorPositionChanged); slotCursorPositionChanged(); } QString KMComposerWin::subject() const { return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText()); } QString KMComposerWin::from() const { return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text()); } QString KMComposerWin::replyTo() const { if (mEdtReplyTo) { return MessageComposer::Util::cleanedUpHeaderString(mEdtReplyTo->text()); } else { return QString(); } } void KMComposerWin::slotInvalidIdentity() { mIncorrectIdentityFolderWarning->identityInvalid(); } void KMComposerWin::slotFccIsInvalid() { mIncorrectIdentityFolderWarning->fccIsInvalid(); } void KMComposerWin::setCurrentTransport(int transportId) { if (!mComposerBase->transportComboBox()->setCurrentTransport(transportId)) { mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); } } void KMComposerWin::setCurrentReplyTo(const QString &replyTo) { if (mEdtReplyTo) { mEdtReplyTo->setText(replyTo); } } uint KMComposerWin::currentIdentity() const { return mComposerBase->identityCombo()->currentIdentity(); } void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg, bool lastSignState, bool lastEncryptState, bool mayAutoSign, bool allowDecryption, bool isModified) { if (!newMsg) { qCDebug(KMAIL_LOG) << "newMsg == 0!"; return; } if (lastSignState) { mLastSignActionState = true; } if (lastEncryptState) { mLastEncryptActionState = true; } mComposerBase->setMessage(newMsg, allowDecryption); mMsg = newMsg; //Add initial data. MessageComposer::PluginEditorConverterInitialData data; data.setMewMsg(mMsg); data.setNewMessage(mContext == TemplateContext::New); mPluginEditorConvertTextManagerInterface->setInitialData(data); KIdentityManagement::IdentityManager *im = KMKernel::self()->identityManager(); mEdtFrom->setText(mMsg->from()->asUnicodeString()); mEdtSubject->setText(mMsg->subject()->asUnicodeString()); // Restore the quote prefix. We can't just use the global quote prefix here, // since the prefix is different for each message, it might for example depend // on the original sender in a reply. if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) { mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString()); } if (auto hrd = newMsg->headerByType("X-KMail-Identity")) { const QString identityStr = hrd->asUnicodeString(); if (!identityStr.isEmpty()) { const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(identityStr.toUInt()); if (ident.isNull()) { if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) { const QString identityStrName = hrd->asUnicodeString(); const KIdentityManagement::Identity id = KMKernel::self()->identityManager()->modifyIdentityForName(identityStrName); if (!id.isNull()) { mId = id.uoid(); } else { mId = 0; } } else { mId = 0; } } else { mId = identityStr.toUInt(); } } } else { if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) { const QString identityStrName = hrd->asUnicodeString(); const KIdentityManagement::Identity id = KMKernel::self()->identityManager()->modifyIdentityForName(identityStrName); if (!id.isNull()) { mId = id.uoid(); } else { mId = 0; } } else { mId = 0; } } // don't overwrite the header values with identity specific values disconnect(mIdentityConnection); // load the mId into the gui, sticky or not, without emitting mComposerBase->identityCombo()->setCurrentIdentity(mId); mIdentityConnection = connect(mComposerBase->identityCombo(), &KIdentityManagement::IdentityCombo::identityChanged, [this](uint val) { slotIdentityChanged(val); }); // manually load the identity's value into the fields; either the one from the // message, where appropriate, or the one from the sticky identity. What's in // mId might have changed meanwhile, thus the save value slotIdentityChanged(mId, true /*initalChange*/); // Fixing the identities with auto signing activated mLastSignActionState = mSignAction->isChecked(); const KIdentityManagement::Identity &ident = im->identityForUoid(mComposerBase->identityCombo()->currentIdentity()); // check for the presence of a DNT header, indicating that MDN's were requested if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) { const QString mdnAddr = hdr->asUnicodeString(); mRequestMDNAction->setChecked((!mdnAddr.isEmpty() && im->thatIsMe(mdnAddr)) || KMailSettings::self()->requestMDN()); } if (auto hdr = newMsg->headerByType("Return-Receipt-To")) { const QString returnReceiptToAddr = hdr->asUnicodeString(); mRequestDeliveryConfirmation->setChecked((!returnReceiptToAddr.isEmpty() - && im->thatIsMe(returnReceiptToAddr)) - /*TODO || KMailSettings::self()->requestMDN()*/); + && im->thatIsMe(returnReceiptToAddr)) + /*TODO || KMailSettings::self()->requestMDN()*/); } // check for presence of a priority header, indicating urgent mail: if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) { const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString(); const QString priority = newMsg->headerByType("Priority")->asUnicodeString(); if (xpriority == QLatin1String("2 (High)") && priority == QLatin1String("urgent")) { mUrgentAction->setChecked(true); } } if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) { mMsg->removeHeader("X-Face"); } else { QString xface = ident.xface(); if (!xface.isEmpty()) { int numNL = (xface.length() - 1) / 70; for (int i = numNL; i > 0; --i) { xface.insert(i * 70, QStringLiteral("\n\t")); } auto header = new KMime::Headers::Generic("X-Face"); header->fromUnicodeString(xface, "utf-8"); mMsg->setHeader(header); } } // if these headers are present, the state of the message should be overruled if (auto hdr = mMsg->headerByType("X-KMail-SignatureActionEnabled")) { mLastSignActionState = (hdr->as7BitString(false).contains("true")); } if (auto hdr = mMsg->headerByType("X-KMail-EncryptActionEnabled")) { mLastEncryptActionState = (hdr->as7BitString(false).contains("true")); } if (auto hdr = mMsg->headerByType("X-KMail-CryptoMessageFormat")) { mCryptoModuleAction->setCurrentItem(format2cb(static_cast( hdr->asUnicodeString().toInt()))); } mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty(); if (QGpgME::openpgp() || QGpgME::smime()) { const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty(); const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty(); setEncryption(mLastEncryptActionState); setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState); } updateSignatureAndEncryptionStateIndicators(); QString kmailFcc; if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) { kmailFcc = hdr->asUnicodeString(); } if (kmailFcc.isEmpty()) { setFcc(ident.fcc()); } else { setFcc(kmailFcc); } if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) { const QString dictionary = hdr->asUnicodeString(); if (!dictionary.isEmpty()) { if (!mComposerBase->dictionary()->assignByDictionnary(dictionary)) { mIncorrectIdentityFolderWarning->dictionaryInvalid(); } } } else { mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); } mEdtReplyTo->setText(mMsg->replyTo()->asUnicodeString()); KMime::Content *msgContent = new KMime::Content; msgContent->setContent(mMsg->encodedContent()); msgContent->parse(); MimeTreeParser::SimpleObjectTreeSource emptySource; MimeTreeParser::ObjectTreeParser otp(&emptySource); //All default are ok emptySource.setDecryptMessage(allowDecryption); otp.parseObjectTree(msgContent); bool shouldSetCharset = false; if ((mContext == Reply || mContext == ReplyToAll || mContext == Forward) && MessageComposer::MessageComposerSettings::forceReplyCharset()) { shouldSetCharset = true; } if (shouldSetCharset && !otp.plainTextContentCharset().isEmpty()) { mOriginalPreferredCharset = otp.plainTextContentCharset(); } // always set auto charset, but prefer original when composing if force reply is set. mCodecAction->setAutoCharset(); delete msgContent; if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign) { // // Espen 2000-05-16 // Delay the signature appending. It may start a fileseletor. // Not user friendy if this modal fileseletor opens before the // composer. // if (MessageComposer::MessageComposerSettings::self()->prependSignature()) { QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature); } else { QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature); } } else { mComposerBase->editor()->externalComposer()->startExternalEditor(); } setModified(isModified); // honor "keep reply in this folder" setting even when the identity is changed later on mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc); QTimer::singleShot(0, this, &KMComposerWin::forceAutoSaveMessage); //Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in. } void KMComposerWin::setAutoSaveFileName(const QString &fileName) { mComposerBase->setAutoSaveFileName(fileName); } void KMComposerWin::setSigningAndEncryptionDisabled(bool v) { mSigningAndEncryptionExplicitlyDisabled = v; } void KMComposerWin::setFolder(const Akonadi::Collection &aFolder) { mFolder = aFolder; } void KMComposerWin::setFcc(const QString &idString) { // check if the sent-mail folder still exists Akonadi::Collection col; if (idString.isEmpty()) { col = CommonKernel->sentCollectionFolder(); } else { col = Akonadi::Collection(idString.toLongLong()); } if (col.isValid()) { mComposerBase->setFcc(col); mFccFolder->setCollection(col); } } bool KMComposerWin::isComposerModified() const { return mComposerBase->editor()->document()->isModified() || mEdtFrom->isModified() || (mEdtReplyTo && mEdtReplyTo->isModified()) || mComposerBase->recipientsEditor()->isModified() || mEdtSubject->document()->isModified(); } bool KMComposerWin::isModified() const { return mWasModified || isComposerModified(); } void KMComposerWin::setModified(bool modified) { mWasModified = modified; changeModifiedState(modified); } void KMComposerWin::changeModifiedState(bool modified) { mComposerBase->editor()->document()->setModified(modified); if (!modified) { mEdtFrom->setModified(false); if (mEdtReplyTo) { mEdtReplyTo->setModified(false); } mComposerBase->recipientsEditor()->clearModified(); mEdtSubject->document()->setModified(false); } } bool KMComposerWin::queryClose() { if (!mComposerBase->editor()->checkExternalEditorFinished()) { return false; } if (kmkernel->shuttingDown() || qApp->isSavingSession()) { writeConfig(); return true; } if (isModified()) { const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder)); const QString savebut = (istemplate ? i18n("Re&save as Template") : i18n("&Save as Draft")); const QString savetext = (istemplate ? i18n("Resave this message in the Templates folder. " "It can then be used at a later time.") : i18n("Save this message in the Drafts folder. " "It can then be edited and sent at a later time.")); const int rc = KMessageBox::warningYesNoCancel(this, i18n("Do you want to save the message for later or discard it?"), i18n("Close Composer"), KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); if (rc == KMessageBox::Cancel) { return false; } else if (rc == KMessageBox::Yes) { // doSend will close the window. Just return false from this method if (istemplate) { slotSaveTemplate(); } else { slotSaveDraft(); } return false; } //else fall through: return true } mComposerBase->cleanupAutoSave(); if (!mMiscComposers.isEmpty()) { qCWarning(KMAIL_LOG) << "Tried to close while composer was active"; return false; } writeConfig(); return true; } MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment() { bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning(); if (!checkForForgottenAttachments) { return MessageComposer::ComposerViewBase::NoMissingAttachmentFound; } mComposerBase->setSubject(subject()); //be sure the composer knows the subject MessageComposer::ComposerViewBase::MissingAttachment missingAttachments = mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords()); return missingAttachments; } void KMComposerWin::forceAutoSaveMessage() { autoSaveMessage(true); } void KMComposerWin::autoSaveMessage(bool force) { if (isComposerModified() || force) { applyComposerSetting(mComposerBase); mComposerBase->saveMailSettings(); mComposerBase->autoSaveMessage(); if (!force) { mWasModified = true; changeModifiedState(false); } } else { mComposerBase->updateAutoSave(); } } bool KMComposerWin::encryptToSelf() const { return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf(); } void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type) { setEnabled(true); if (!msg.isEmpty()) { KMessageBox::sorry(mMainWidget, msg, (type == MessageComposer::ComposerViewBase::AutoSave) ? i18n("Autosave Message Failed") : i18n("Sending Message Failed")); } } void KMComposerWin::slotSendSuccessful() { setModified(false); mComposerBase->cleanupAutoSave(); mFolder = Akonadi::Collection(); // see dtor close(); } const KIdentityManagement::Identity &KMComposerWin::identity() const { return KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity()); } Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const { if (!mCryptoModuleAction) { return Kleo::AutoFormat; } return cb2format(mCryptoModuleAction->currentItem()); } void KMComposerWin::addAttach(KMime::Content *msgPart) { mComposerBase->addAttachmentPart(msgPart); setModified(true); } void KMComposerWin::slotAddressBook() { KRun::runCommand(QStringLiteral("kaddressbook"), window()); } void KMComposerWin::slotInsertFile() { const QUrl u = insertFile(); if (u.isEmpty()) { return; } mRecentAction->addUrl(u); // Prevent race condition updating list when multiple composers are open { QUrlQuery query; const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset"))); QStringList urls = KMailSettings::self()->recentUrls(); QStringList encodings = KMailSettings::self()->recentEncodings(); // Prevent config file from growing without bound // Would be nicer to get this constant from KRecentFilesAction const int mMaxRecentFiles = 30; while (urls.count() > mMaxRecentFiles) { urls.removeLast(); } while (encodings.count() > mMaxRecentFiles) { encodings.removeLast(); } // sanity check if (urls.count() != encodings.count()) { urls.clear(); encodings.clear(); } urls.prepend(u.toDisplayString()); encodings.prepend(encoding); KMailSettings::self()->setRecentUrls(urls); KMailSettings::self()->setRecentEncodings(encodings); mRecentAction->saveEntries(KMKernel::self()->config()->group(QString())); } slotInsertRecentFile(u); } void KMComposerWin::slotRecentListFileClear() { KSharedConfig::Ptr config = KMKernel::self()->config(); KConfigGroup group(config, "Composer"); group.deleteEntry("recent-urls"); group.deleteEntry("recent-encodings"); mRecentAction->saveEntries(config->group(QString())); } void KMComposerWin::slotInsertRecentFile(const QUrl &u) { if (u.fileName().isEmpty()) { return; } // Get the encoding previously used when inserting this file QString encoding; const QStringList urls = KMailSettings::self()->recentUrls(); const QStringList encodings = KMailSettings::self()->recentEncodings(); const int index = urls.indexOf(u.toDisplayString()); if (index != -1) { encoding = encodings[ index ]; } else { qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; //see InsertTextFileJob return; } MessageComposer::InsertTextFileJob *job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u); job->setEncoding(encoding); connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile); job->start(); } bool KMComposerWin::showErrorMessage(KJob *job) { if (job->error()) { if (static_cast(job)->uiDelegate()) { static_cast(job)->uiDelegate()->showErrorMessage(); } else { qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString(); } return true; } return false; } void KMComposerWin::slotInsertTextFile(KJob *job) { showErrorMessage(job); } void KMComposerWin::slotCryptoModuleSelected() { slotSelectCryptoModule(false); } void KMComposerWin::slotSelectCryptoModule(bool init) { if (!init) { setModified(true); } mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments()); mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments()); } void KMComposerWin::slotUpdateFont() { if (!mFixedFontAction) { return; } const QFont plaintextFont = mFixedFontAction->isChecked() ? mFixedFont : mBodyFont; mComposerBase->editor()->composerControler()->setFontForWholeText(plaintextFont); mComposerBase->editor()->setDefaultFontSize(plaintextFont.pointSize()); } QUrl KMComposerWin::insertFile() { const KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlAndEncoding(QString(), QUrl(), QString(), this, i18nc("@title:window", "Insert File")); QUrl url; if (!result.URLs.isEmpty()) { url = result.URLs.constFirst(); if (url.isValid()) { MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding)); } } return url; } QString KMComposerWin::smartQuote(const QString &msg) { return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth()); } void KMComposerWin::insertUrls(const QMimeData *source, const QList &urlList) { QStringList urlAdded; for (const QUrl &url : urlList) { QString urlStr; if (url.scheme() == QLatin1String("mailto")) { urlStr = KEmailAddress::decodeMailtoUrl(url); } else { urlStr = url.toDisplayString(); // Workaround #346370 if (urlStr.isEmpty()) { urlStr = source->text(); } } if (!urlAdded.contains(urlStr)) { mComposerBase->editor()->composerControler()->insertLink(urlStr); urlAdded.append(urlStr); } } } bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment) { // If this is a PNG image, either add it as an attachment or as an inline image if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) { const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html"))); mComposerBase->editor()->insertHtml(html); return true; } else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText() && !forceAttachment) { mComposerBase->editor()->insertPlainText(source->text()); return true; } else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) { // Get the image data before showing the dialog, since that processes events which can delete // the QMimeData object behind our back const QByteArray imageData = source->data(QStringLiteral("image/png")); if (imageData.isEmpty()) { return true; } if (!forceAttachment) { if (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) { QImage image = qvariant_cast(source->imageData()); QFileInfo fi(source->text()); QMenu menu(this); const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image")); /*const QAction *addAsAttachmentAction = */ menu.addAction(i18n("Add as &Attachment")); const QAction *selectedAction = menu.exec(QCursor::pos()); if (selectedAction == addAsInlineImageAction) { // Let the textedit from kdepimlibs handle inline images mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi); return true; } else if (!selectedAction) { return true; } // else fall through } } // Ok, when we reached this point, the user wants to add the image as an attachment. // Ask for the filename first. bool ok; QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok); if (!ok) { return true; } attName = attName.trimmed(); if (attName.isEmpty()) { KMessageBox::sorry(this, i18n("Attachment name can't be empty"), i18n("Invalid Attachment Name")); return true; } addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png"); return true; } else { DndFromArkJob *job = new DndFromArkJob(this); job->setComposerWin(this); if (job->extract(source)) { return true; } } // If this is a URL list, add those files as attachments or text // but do not offer this if we are pasting plain text containing an url, e.g. from a browser const QList urlList = source->urls(); if (!urlList.isEmpty()) { //Search if it's message items. Akonadi::Item::List items; Akonadi::Collection::List collections; bool allLocalURLs = true; for (const QUrl &url : urlList) { if (!url.isLocalFile()) { allLocalURLs = false; } const Akonadi::Item item = Akonadi::Item::fromUrl(url); if (item.isValid()) { items << item; } else { const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url); if (collection.isValid()) { collections << collection; } } } if (items.isEmpty() && collections.isEmpty()) { if (allLocalURLs || forceAttachment) { for (const QUrl &url : urlList) { addAttachment(url, QString()); } } else { QMenu p; const int sizeUrl(urlList.size()); const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl)); const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl)); const QAction *selectedAction = p.exec(QCursor::pos()); if (selectedAction == addAsTextAction) { insertUrls(source, urlList); } else if (selectedAction == addAsAttachmentAction) { for (const QUrl &url : urlList) { if (url.isValid()) { addAttachment(url, QString()); } } } } return true; } else { if (!items.isEmpty()) { Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(items, this); itemFetchJob->fetchScope().fetchFullPayload(true); itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob); } if (!collections.isEmpty()) { qCDebug(KMAIL_LOG) << "Collection dnd not supported"; } return true; } } return false; } void KMComposerWin::slotPasteAsAttachment() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); if (insertFromMimeData(mimeData, true)) { return; } if (mimeData->hasText()) { bool ok; const QString attName = QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok); if (ok) { mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain"); } return; } } void KMComposerWin::slotFetchJob(KJob *job) { if (showErrorMessage(job)) { return; } Akonadi::ItemFetchJob *fjob = qobject_cast(job); if (!fjob) { return; } const Akonadi::Item::List items = fjob->items(); if (items.isEmpty()) { return; } if (items.first().mimeType() == KMime::Message::mimeType()) { uint identity = 0; if (items.at(0).isValid()) { const Akonadi::Collection parentCollection = items.at(0).parentCollection(); if (parentCollection.isValid()) { const QString resourceName = parentCollection.resource(); if (!resourceName.isEmpty()) { QSharedPointer fd(MailCommon::FolderSettings::forCollection(parentCollection, false)); if (fd) { identity = fd->identity(); } } } } KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this); command->start(); } else { for (const Akonadi::Item &item : items) { QString attachmentName = QStringLiteral("attachment"); if (item.hasPayload()) { const KContacts::Addressee contact = item.payload(); attachmentName = contact.realName() + QLatin1String(".vcf"); //Workaround about broken kaddressbook fields. QByteArray data = item.payloadData(); KContacts::adaptIMAttributes(data); addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard"); } else if (item.hasPayload()) { const KContacts::ContactGroup group = item.payload(); attachmentName = group.name() + QLatin1String(".vcf"); Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(group, this); expandJob->setProperty("groupName", attachmentName); connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult); expandJob->start(); } else { addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1()); } } } } void KMComposerWin::slotExpandGroupResult(KJob *job) { Akonadi::ContactGroupExpandJob *expandJob = qobject_cast(job); Q_ASSERT(expandJob); const QString attachmentName = expandJob->property("groupName").toString(); KContacts::VCardConverter converter; const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0); if (!groupData.isEmpty()) { addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard"); } } void KMComposerWin::slotClose() { close(); } void KMComposerWin::slotNewComposer() { KMComposerCreateNewComposerJob *job = new KMComposerCreateNewComposerJob; job->setCollectionForNewMessage(mCollectionForNewMessage); job->setCurrentIdentity(currentIdentity()); job->start(); } void KMComposerWin::slotUpdateWindowTitle() { QString s(mEdtSubject->toPlainText()); // Remove characters that show badly in most window decorations: // newlines tend to become boxes. if (s.isEmpty()) { setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')')); } else { setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' '))); } } void KMComposerWin::slotEncryptToggled(bool on) { setEncryption(on, true); updateSignatureAndEncryptionStateIndicators(); } void KMComposerWin::setEncryption(bool encrypt, bool setByUser) { bool wasModified = isModified(); if (setByUser) { setModified(true); } if (!mEncryptAction->isEnabled()) { encrypt = false; } // check if the user wants to encrypt messages to himself and if he defined // an encryption key for the current identity else if (encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey) { if (setByUser) { KMessageBox::sorry(this, i18n("

You have requested that messages be " "encrypted to yourself, but the currently selected " "identity does not define an (OpenPGP or S/MIME) " "encryption key to use for this.

" "

Please select the key(s) to use " "in the identity configuration.

" "
"), i18n("Undefined Encryption Key")); setModified(wasModified); } encrypt = false; } // make sure the mEncryptAction is in the right state mEncryptAction->setChecked(encrypt); mEncryptAction->setProperty("setByUser", setByUser); if (!setByUser) { updateSignatureAndEncryptionStateIndicators(); } // show the appropriate icon if (encrypt) { mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt"))); } else { mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-decrypt"))); } if (setByUser) { // User has toggled encryption, go over all recipients Q_FOREACH (auto line, mComposerBase->recipientsEditor()->lines()) { if (encrypt) { // Encryption was enabled, update encryption status of all recipients slotRecipientAdded(qobject_cast(line)); } else { // Encryption was disabled, remove the encryption indicator auto edit = qobject_cast(line); edit->setIcon(QIcon()); auto recipient = edit->data().dynamicCast(); recipient->setEncryptionAction(Kleo::Impossible); recipient->setKey(GpgME::Key()); } } } // mark the attachments for (no) encryption if (canSignEncryptAttachments()) { mComposerBase->attachmentModel()->setEncryptSelected(encrypt); } } void KMComposerWin::slotSignToggled(bool on) { setSigning(on, true); updateSignatureAndEncryptionStateIndicators(); } void KMComposerWin::setSigning(bool sign, bool setByUser) { bool wasModified = isModified(); if (setByUser) { setModified(true); } if (!mSignAction->isEnabled()) { sign = false; } // check if the user defined a signing key for the current identity if (sign && !mLastIdentityHasSigningKey) { if (setByUser) { KMessageBox::sorry(this, i18n("

In order to be able to sign " "this message you first have to " "define the (OpenPGP or S/MIME) signing key " "to use.

" "

Please select the key to use " "in the identity configuration.

" "
"), i18n("Undefined Signing Key")); setModified(wasModified); } sign = false; } // make sure the mSignAction is in the right state mSignAction->setChecked(sign); if (!setByUser) { updateSignatureAndEncryptionStateIndicators(); } // mark the attachments for (no) signing if (canSignEncryptAttachments()) { mComposerBase->attachmentModel()->setSignSelected(sign); } } void KMComposerWin::slotWordWrapToggled(bool on) { if (on) { mComposerBase->editor()->enableWordWrap(validateLineWrapWidth()); } else { disableWordWrap(); } } int KMComposerWin::validateLineWrapWidth() { int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth(); if ((lineWrap == 0) || (lineWrap > 78)) { lineWrap = 78; } else if (lineWrap < 30) { lineWrap = 30; } return lineWrap; } void KMComposerWin::disableWordWrap() { mComposerBase->editor()->disableWordWrap(); } void KMComposerWin::forceDisableHtml() { mForceDisableHtml = true; disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); mMarkupAction->setEnabled(false); // FIXME: Remove the toggle toolbar action somehow } bool KMComposerWin::isComposing() const { return mComposerBase && mComposerBase->isComposing(); } void KMComposerWin::disableForgottenAttachmentsCheck() { mCheckForForgottenAttachments = false; } void KMComposerWin::slotPrint() { printComposer(false); } void KMComposerWin::slotPrintPreview() { printComposer(true); } void KMComposerWin::printComposer(bool preview) { MessageComposer::Composer *composer = createSimpleComposer(); mMiscComposers.append(composer); composer->setProperty("preview", preview); connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult); composer->start(); } void KMComposerWin::slotPrintComposeResult(KJob *job) { const bool preview = job->property("preview").toBool(); printComposeResult(job, preview); } void KMComposerWin::printComposeResult(KJob *job, bool preview) { Q_ASSERT(dynamic_cast< MessageComposer::Composer * >(job)); MessageComposer::Composer *composer = dynamic_cast< MessageComposer::Composer * >(job); Q_ASSERT(mMiscComposers.contains(composer)); mMiscComposers.removeAll(composer); if (composer->error() == MessageComposer::Composer::NoError) { Q_ASSERT(composer->resultMessages().size() == 1); Akonadi::Item printItem; printItem.setPayload(composer->resultMessages().constFirst()); Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().constFirst()), printItem); const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich; const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text; KMPrintCommandInfo commandInfo; commandInfo.mMsg = printItem; commandInfo.mFormat = format; commandInfo.mHtmlLoadExtOverride = isHtml; commandInfo.mPrintPreview = preview; KMPrintCommand *command = new KMPrintCommand(this, commandInfo); command->start(); } else { showErrorMessage(job); } } void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool willSendItWithoutReediting) { //TODO generate new message from plugins. MessageComposer::PluginEditorConverterBeforeConvertingData data; data.setNewMessage(mContext == TemplateContext::New); mPluginEditorConvertTextManagerInterface->setDataBeforeConvertingText(data); //TODO converttext if necessary // TODO integrate with MDA online status if (method == MessageComposer::MessageSender::SendImmediate) { if (!MessageComposer::Util::sendMailDispatcherIsOnline()) { method = MessageComposer::MessageSender::SendLater; } } if (saveIn == MessageComposer::MessageSender::SaveInNone || willSendItWithoutReediting) { // don't save as draft or template, send immediately if (KEmailAddress::firstEmailAddress(from()).isEmpty()) { if (!(mShowHeaders & HDR_FROM)) { mShowHeaders |= HDR_FROM; rethinkFields(false); } mEdtFrom->setFocus(); KMessageBox::sorry(this, i18n("You must enter your email address in the " "From: field. You should also set your email " "address for all identities, so that you do " "not have to enter it for each message.")); return; } if (mComposerBase->to().isEmpty()) { if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) { KMessageBox::information(this, i18n("You must specify at least one receiver, " "either in the To: field or as CC or as BCC.")); return; } else { int rc = KMessageBox::questionYesNo(this, i18n("To: field is empty. " "Send message anyway?"), i18n("No To: specified"), KStandardGuiItem::yes(), KStandardGuiItem::no()); if (rc == KMessageBox::No) { return; } } } if (subject().isEmpty()) { mEdtSubject->setFocus(); int rc = KMessageBox::questionYesNo(this, i18n("You did not specify a subject. " "Send message anyway?"), i18n("No Subject Specified"), KGuiItem(i18n("S&end as Is")), KGuiItem(i18n("&Specify the Subject"))); if (rc == KMessageBox::No) { return; } } const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment(); if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) { return; } MessageComposer::PluginEditorCheckBeforeSendParams params; params.setSubject(subject()); params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); params.setIdentity(mComposerBase->identityCombo()->currentIdentity()); params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0); params.setTransportId(mComposerBase->transportComboBox()->currentTransportId()); const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(mComposerBase->identityCombo()->currentIdentity()); QString defaultDomainName; if (!ident.isNull()) { defaultDomainName = ident.defaultDomainName(); } const QString composerBaseBccTrimmed = mComposerBase->bcc().trimmed(); const QString composerBaseToTrimmed = mComposerBase->to().trimmed(); const QString composerBaseCcTrimmed = mComposerBase->cc().trimmed(); params.setBccAddresses(composerBaseBccTrimmed); params.setToAddresses(composerBaseToTrimmed); params.setCcAddresses(composerBaseCcTrimmed); params.setDefaultDomain(defaultDomainName); if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) { return; } const QStringList recipients = {composerBaseToTrimmed, composerBaseCcTrimmed, composerBaseBccTrimmed}; setEnabled(false); // Validate the To:, CC: and BCC fields AddressValidationJob *job = new AddressValidationJob(recipients.join(QStringLiteral(", ")), this, this); job->setDefaultDomain(defaultDomainName); job->setProperty("method", static_cast(method)); job->setProperty("saveIn", static_cast(saveIn)); connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend); job->start(); // we'll call send from within slotDoDelaySend } else { if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptAction->isChecked() && KMailSettings::self()->alwaysEncryptDrafts() && mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) { KMessageBox::information(this, i18n("You must specify at least one receiver " "in order to be able to encrypt a draft.")); return; } doDelayedSend(method, saveIn); } } void KMComposerWin::slotDoDelayedSend(KJob *job) { if (job->error()) { KMessageBox::error(this, job->errorText()); setEnabled(true); return; } const AddressValidationJob *validateJob = qobject_cast(job); // Abort sending if one of the recipient addresses is invalid ... if (!validateJob->isValid()) { setEnabled(true); return; } // ... otherwise continue as usual const MessageComposer::MessageSender::SendMethod method = static_cast(job->property("method").toInt()); const MessageComposer::MessageSender::SaveIn saveIn = static_cast(job->property("saveIn").toInt()); doDelayedSend(method, saveIn); } void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase) { QList< QByteArray > charsets = mCodecAction->mimeCharsets(); if (!mOriginalPreferredCharset.isEmpty()) { charsets.insert(0, mOriginalPreferredCharset); } mComposerBase->setFrom(from()); mComposerBase->setReplyTo(replyTo()); mComposerBase->setSubject(subject()); mComposerBase->setCharsets(charsets); mComposerBase->setUrgent(mUrgentAction->isChecked()); mComposerBase->setMDNRequested(mRequestMDNAction->isChecked()); mComposerBase->setRequestDeleveryConfirmation(mRequestDeliveryConfirmation->isChecked()); } void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn) { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif applyComposerSetting(mComposerBase); if (mForceDisableHtml) { disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); } bool sign = mSignAction->isChecked(); bool encrypt = mEncryptAction->isChecked(); mComposerBase->setCryptoOptions(sign, encrypt, cryptoMessageFormat(), ((saveIn != MessageComposer::MessageSender::SaveInNone && !KMailSettings::self()->alwaysEncryptDrafts()) || mSigningAndEncryptionExplicitlyDisabled)); const int num = KMailSettings::self()->customMessageHeadersCount(); QMap customHeader; for (int ix = 0; ix < num; ++ix) { CustomMimeHeader customMimeHeader(QString::number(ix)); customMimeHeader.load(); customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue()); } QMap::const_iterator extraCustomHeader = mExtraHeaders.constBegin(); while (extraCustomHeader != mExtraHeaders.constEnd()) { customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value()); ++extraCustomHeader; } mComposerBase->setCustomHeader(customHeader); mComposerBase->send(method, saveIn, false); } void KMComposerWin::slotSendLater() { if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) { return; } if (!checkRecipientNumber()) { return; } mComposerBase->setSendLaterInfo(nullptr); if (mComposerBase->editor()->checkExternalEditorFinished()) { const bool wasRegistered = (SendLater::SendLaterUtil::sentLaterAgentWasRegistered() && SendLater::SendLaterUtil::sentLaterAgentEnabled()); if (wasRegistered) { SendLater::SendLaterInfo *info = nullptr; QPointer dlg = new SendLater::SendLaterDialog(info, this); if (dlg->exec()) { info = dlg->info(); const SendLater::SendLaterDialog::SendLaterAction action = dlg->action(); delete dlg; switch (action) { case SendLater::SendLaterDialog::Unknown: qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it."; break; case SendLater::SendLaterDialog::Canceled: return; break; case SendLater::SendLaterDialog::PutInOutbox: doSend(MessageComposer::MessageSender::SendLater); break; case SendLater::SendLaterDialog::SendDeliveryAtTime: mComposerBase->setSendLaterInfo(info); if (info->isRecurrence()) { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates, true); } else { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts, true); } break; } } else { delete dlg; } } else { doSend(MessageComposer::MessageSender::SendLater); } } } void KMComposerWin::slotSaveDraft() { if (mComposerBase->editor()->checkExternalEditorFinished()) { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts); } } void KMComposerWin::slotSaveTemplate() { if (mComposerBase->editor()->checkExternalEditorFinished()) { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates); } } void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport) { if (transport) { mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); slotSendNow(); } } void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport) { if (transport) { mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); slotSendLater(); } } void KMComposerWin::sendNow(bool shortcutUsed) { if (!mComposerBase->editor()->checkExternalEditorFinished()) { return; } if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) { return; } if (!checkRecipientNumber()) { return; } mSendNowByShortcutUsed = shortcutUsed; if (KMailSettings::self()->checkSpellingBeforeSend()) { mComposerBase->editor()->forceSpellChecking(); } else { slotCheckSendNow(); } } void KMComposerWin::slotSendNowByShortcut() { sendNow(true); } void KMComposerWin::slotSendNow() { sendNow(false); } void KMComposerWin::confirmBeforeSend() { const int rc = KMessageBox::warningYesNoCancel(mMainWidget, i18n("About to send email..."), i18n("Send Confirmation"), KGuiItem(i18n("&Send Now")), KGuiItem(i18n("Send &Later"))); if (rc == KMessageBox::Yes) { doSend(MessageComposer::MessageSender::SendImmediate); } else if (rc == KMessageBox::No) { doSend(MessageComposer::MessageSender::SendLater); } } void KMComposerWin::slotCheckSendNowStep2() { if (KMailSettings::self()->confirmBeforeSend()) { confirmBeforeSend(); } else { if (mSendNowByShortcutUsed) { if (!KMailSettings::self()->checkSendDefaultActionShortcut()) { ValidateSendMailShortcut validateShortcut(actionCollection(), this); if (!validateShortcut.validate()) { return; } } if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) { confirmBeforeSend(); return; } } doSend(MessageComposer::MessageSender::SendImmediate); } } void KMComposerWin::slotDelayedCheckSendNow() { QTimer::singleShot(0, this, &KMComposerWin::slotCheckSendNow); } void KMComposerWin::slotCheckSendNow() { PotentialPhishingEmailJob *job = new PotentialPhishingEmailJob(this); KConfigGroup group(KSharedConfig::openConfig(), "PotentialPhishing"); const QStringList whiteList = group.readEntry("whiteList", QStringList()); job->setEmailWhiteList(whiteList); QStringList lst; lst << mComposerBase->to(); if (!mComposerBase->cc().isEmpty()) { lst << mComposerBase->cc().split(QLatin1Char(',')); } if (!mComposerBase->bcc().isEmpty()) { lst << mComposerBase->bcc().split(QLatin1Char(',')); } job->setPotentialPhishingEmails(lst); connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound); job->start(); } void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list) { if (list.isEmpty()) { slotCheckSendNowStep2(); } else { mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list); } } bool KMComposerWin::checkRecipientNumber() const { const int thresHold = KMailSettings::self()->recipientThreshold(); if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) { if (KMessageBox::questionYesNo(mMainWidget, i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold), i18n("Too many recipients"), KGuiItem(i18n("&Send as Is")), KGuiItem(i18n("&Edit Recipients"))) == KMessageBox::No) { return false; } } return true; } void KMComposerWin::enableHtml() { if (mForceDisableHtml) { disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); return; } mComposerBase->editor()->activateRichText(); if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) { // Use singleshot, as we we might actually be called from a slot that wanted to disable the // toolbar (but the messagebox in disableHtml() prevented that and called us). // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled" // signal, so wait one event loop run for that. QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show); } if (!mMarkupAction->isChecked()) { mMarkupAction->setChecked(true); } mComposerBase->editor()->composerActions()->updateActionStates(); mComposerBase->editor()->composerActions()->setActionsEnabled(true); } void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation) { bool forcePlainTextMarkup = false; if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed() && !mForceDisableHtml) { int choice = KMessageBox::warningYesNoCancel(this, i18n("Turning HTML mode off " "will cause the text to lose the formatting. Are you sure?"), i18n("Lose the formatting?"), KGuiItem(i18n("Lose Formatting")), KGuiItem(i18n("Add Markup Plain Text")), KStandardGuiItem::cancel(), QStringLiteral("LoseFormattingWarning")); switch (choice) { case KMessageBox::Cancel: enableHtml(); return; case KMessageBox::No: forcePlainTextMarkup = true; break; case KMessageBox::Yes: break; } } mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup); mComposerBase->editor()->switchToPlainText(); mComposerBase->editor()->composerActions()->setActionsEnabled(false); slotUpdateFont(); if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) { // See the comment in enableHtml() why we use a singleshot timer, similar situation here. QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide); } if (mMarkupAction->isChecked()) { mMarkupAction->setChecked(false); } } void KMComposerWin::slotToggleMarkup() { htmlToolBarVisibilityChanged(mMarkupAction->isChecked()); } void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode) { if (mode == MessageComposer::RichTextComposerNg::Plain) { disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all? } else { enableHtml(); } } void KMComposerWin::htmlToolBarVisibilityChanged(bool visible) { if (visible) { enableHtml(); } else { disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm); } } void KMComposerWin::slotAutoSpellCheckingToggled(bool on) { mAutoSpellCheckingAction->setChecked(on); if (on != mComposerBase->editor()->checkSpellingEnabled()) { mComposerBase->editor()->setCheckSpellingEnabled(on); } if (on != mEdtSubject->checkSpellingEnabled()) { mEdtSubject->setCheckSpellingEnabled(on); } mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on); } void KMComposerWin::slotSpellCheckingStatus(const QString &status) { mStatusbarLabel->setText(status); QTimer::singleShot(2000, this, &KMComposerWin::slotSpellcheckDoneClearStatus); } void KMComposerWin::slotSpellcheckDoneClearStatus() { mStatusbarLabel->clear(); } void KMComposerWin::slotIdentityChanged(uint uoid, bool initialChange) { if (!mMsg) { qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!"; return; } const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(uoid); if (ident.isNull()) { return; } bool wasModified(isModified()); Q_EMIT identityChanged(identity()); if (!ident.fullEmailAddr().isNull()) { mEdtFrom->setText(ident.fullEmailAddr()); } // make sure the From field is shown if it does not contain a valid email address if (KEmailAddress::firstEmailAddress(from()).isEmpty()) { mShowHeaders |= HDR_FROM; } if (mEdtReplyTo) { mEdtReplyTo->setText(ident.replyToAddr()); } // remove BCC of old identity and add BCC of new identity (if they differ) const KIdentityManagement::Identity &oldIdentity = KMKernel::self()->identityManager()->identityForUoidOrDefault(mId); if (ident.organization().isEmpty()) { mMsg->removeHeader(); } else { KMime::Headers::Organization *const organization = new KMime::Headers::Organization; organization->fromUnicodeString(ident.organization(), "utf-8"); mMsg->setHeader(organization); } if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) { mMsg->removeHeader("X-Face"); } else { QString xface = ident.xface(); if (!xface.isEmpty()) { int numNL = (xface.length() - 1) / 70; for (int i = numNL; i > 0; --i) { xface.insert(i * 70, QStringLiteral("\n\t")); } KMime::Headers::Generic *header = new KMime::Headers::Generic("X-Face"); header->fromUnicodeString(xface, "utf-8"); mMsg->setHeader(header); } } - if (initialChange) { if (auto hrd = mMsg->headerByType("X-KMail-Transport")) { const QString mailtransportStr = hrd->asUnicodeString(); if (!mailtransportStr.isEmpty()) { int transportId = mailtransportStr.toInt(); const Transport *transport = TransportManager::self()->transportById(transportId, false); /*don't return default transport */ if (transport) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transport->id()), "utf-8"); mMsg->setHeader(header); mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); } else { if (auto hrd = mMsg->headerByType("X-KMail-Transport-Name")) { const QString identityStrName = hrd->asUnicodeString(); const Transport *transport = TransportManager::self()->transportByName(identityStrName, true); if (transport) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transport->id()), "utf-8"); mMsg->setHeader(header); mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); } else { mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); } } else { mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); } } } } else { const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); const Transport *transport = TransportManager::self()->transportById(transportId, true); if (transport) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transport->id()), "utf-8"); mMsg->setHeader(header); mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); } else { mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); } - } } else { const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); const Transport *transport = TransportManager::self()->transportById(transportId, true); if (!transport) { mMsg->removeHeader("X-KMail-Transport"); mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); } else { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transport->id()), "utf-8"); mMsg->setHeader(header); mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); } } const bool fccIsDisabled = ident.disabledFcc(); if (fccIsDisabled) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled"); header->fromUnicodeString(QStringLiteral("true"), "utf-8"); mMsg->setHeader(header); } else { mMsg->removeHeader("X-KMail-FccDisabled"); } mFccFolder->setEnabled(!fccIsDisabled); mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary()); if (!mPreventFccOverwrite) { setFcc(ident.fcc()); } // if unmodified, apply new template, if one is set if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) && !initialChange) { applyTemplate(uoid, mId, ident, wasModified); } else { mComposerBase->identityChanged(ident, oldIdentity, false); mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage()); updateComposerAfterIdentityChanged(ident, uoid, wasModified); } } void KMComposerWin::updateComposerAfterIdentityChanged(const KIdentityManagement::Identity &ident, uint uoid, bool wasModified) { // disable certain actions if there is no PGP user identity set // for this profile bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); // save the state of the sign and encrypt button if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) { mLastEncryptActionState = mEncryptAction->isChecked(); setEncryption(false); } if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) { mLastSignActionState = mSignAction->isChecked(); setSigning(false); } // restore the last state of the sign and encrypt button if (bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey) { setEncryption(mLastEncryptActionState); } if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) { setSigning(mLastSignActionState); } mCryptoModuleAction->setCurrentItem(format2cb( Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat()))); slotSelectCryptoModule(true); mLastIdentityHasSigningKey = bNewIdentityHasSigningKey; mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey; const KIdentityManagement::Signature sig = const_cast(ident).signature(); bool isEnabledSignature = sig.isEnabledSignature(); mAppendSignature->setEnabled(isEnabledSignature); mPrependSignature->setEnabled(isEnabledSignature); mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature); mId = uoid; changeCryptoAction(); // make sure the From and BCC fields are shown if necessary rethinkFields(false); setModified(wasModified); } void KMComposerWin::slotSpellcheckConfig() { static_cast(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc")); } void KMComposerWin::slotEditToolbars() { QPointer dlg = new KEditToolBar(guiFactory(), this); connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars); dlg->exec(); delete dlg; } void KMComposerWin::slotUpdateToolbars() { createGUI(QStringLiteral("kmcomposerui.rc")); applyMainWindowSettings(KMKernel::self()->config()->group("Composer")); } void KMComposerWin::slotEditKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsDisallowed); } void KMComposerWin::setFocusToEditor() { // The cursor position is already set by setMsg(), so we only need to set the // focus here. mComposerBase->editor()->setFocus(); } void KMComposerWin::setFocusToSubject() { mEdtSubject->setFocus(); } void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode) { KMailSettings::self()->setCompletionMode((int)mode); // sync all the lineedits to the same completion mode mEdtFrom->setCompletionMode(mode); mEdtReplyTo->setCompletionMode(mode); mComposerBase->recipientsEditor()->setCompletionMode(mode); } void KMComposerWin::slotConfigChanged() { readConfig(true /*reload*/); mComposerBase->updateAutoSave(); rethinkFields(); slotWordWrapToggled(mWordWrapAction->isChecked()); } /* * checks if the drafts-folder has been deleted * that is not nice so we set the system-drafts-folder */ void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col) { qCDebug(KMAIL_LOG) << "you killed me."; // TODO: need to handle templates here? if ((mFolder.isValid()) && (col.id() == mFolder.id())) { mFolder = CommonKernel->draftsCollectionFolder(); qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id(); } else if (col.id() == mFccFolder->collection().id()) { qCDebug(KMAIL_LOG) << "FCC was removed " << col.id(); mFccFolder->setCollection(CommonKernel->sentCollectionFolder()); mIncorrectIdentityFolderWarning->fccIsInvalid(); } } void KMComposerWin::slotOverwriteModeChanged() { const bool overwriteMode = mComposerBase->editor()->overwriteMode(); mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1); mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode); } void KMComposerWin::slotCursorPositionChanged() { // Change Line/Column info in status bar const int line = mComposerBase->editor()->linePosition() + 1; const int col = mComposerBase->editor()->columnNumber() + 1; QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line); mCursorLineLabel->setText(temp); temp = i18n(" Column: %1 ", col); mCursorColumnLabel->setText(temp); // Show link target in status bar if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) { const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1String(" -> ") + mComposerBase->editor()->composerControler()->currentLinkUrl(); mStatusbarLabel->setText(text); } else { mStatusbarLabel->clear(); } } void KMComposerWin::recipientEditorSizeHintChanged() { QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize); } void KMComposerWin::setMaximumHeaderSize() { mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height()); } void KMComposerWin::updateSignatureAndEncryptionStateIndicators() { mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(mSignAction->isChecked(), mEncryptAction->isChecked()); } void KMComposerWin::slotDictionaryLanguageChanged(const QString &language) { mComposerBase->dictionary()->setCurrentByDictionary(language); } void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection) { mComposerBase->setFcc(collection); mComposerBase->editor()->document()->setModified(true); } void KMComposerWin::slotSaveAsFile() { SaveAsFileJob *job = new SaveAsFileJob(this); job->setParentWidget(this); job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); job->setTextDocument(mComposerBase->editor()->document()); job->start(); //not necessary to delete it. It done in SaveAsFileJob } void KMComposerWin::slotAttachMissingFile() { mComposerBase->attachmentController()->showAddAttachmentFileDialog(); } void KMComposerWin::slotVerifyMissingAttachmentTimeout() { if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) { mAttachmentMissing->animatedShow(); } } void KMComposerWin::slotExplicitClosedMissingAttachment() { if (mVerifyMissingAttachment) { mVerifyMissingAttachment->stop(); delete mVerifyMissingAttachment; mVerifyMissingAttachment = nullptr; } } void KMComposerWin::addExtraCustomHeaders(const QMap &headers) { mExtraHeaders = headers; } void KMComposerWin::slotExternalEditorStarted() { mComposerBase->identityCombo()->setEnabled(false); mExternalEditorWarning->show(); } void KMComposerWin::slotExternalEditorClosed() { mComposerBase->identityCombo()->setEnabled(true); mExternalEditorWarning->hide(); } void KMComposerWin::slotInsertShortUrl(const QString &url) { mComposerBase->editor()->composerControler()->insertLink(url); } void KMComposerWin::slotTransportChanged() { mComposerBase->editor()->document()->setModified(true); } void KMComposerWin::slotFollowUpMail(bool toggled) { if (toggled) { QPointer dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this); if (dlg->exec()) { mComposerBase->setFollowUpDate(dlg->selectedDate()); mComposerBase->setFollowUpCollection(dlg->collection()); } else { mFollowUpToggleAction->setChecked(false); } delete dlg; } else { mComposerBase->clearFollowUp(); } } void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b) { mSnippetWidget->setVisible(b); mSnippetSplitterCollapser->setVisible(b); } void KMComposerWin::slotOverwriteModeWasChanged(bool state) { mComposerBase->editor()->setCursorWidth(state ? 5 : 1); mComposerBase->editor()->setOverwriteMode(state); } QList KMComposerWin::customToolsList() const { return mCustomToolsWidget->actionList(); } QList KMComposerWin::pluginToolsActionListForPopupMenu() const { return mPluginEditorManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu); } void KMComposerWin::slotRecipientEditorLineAdded(KPIM::MultiplyingLine *line_) { auto line = qobject_cast(line_); Q_ASSERT(line); connect(line, &MessageComposer::RecipientLineNG::countChanged, this, [this, line]() { this->slotRecipientAdded(line); }); connect(line, &MessageComposer::RecipientLineNG::iconClicked, this, [this, line]() { this->slotRecipientLineIconClicked(line); }); connect(line, &MessageComposer::RecipientLineNG::destroyed, this, &KMComposerWin::slotRecipientEditorFocusChanged, Qt::QueuedConnection); connect(line, &MessageComposer::RecipientLineNG::activeChanged, this, [this, line]() { this->slotRecipientFocusLost(line); }); slotRecipientEditorFocusChanged(); } void KMComposerWin::slotRecipientEditorFocusChanged() { // Already disabled if (mEncryptAction->property("setByUser").toBool()) { return; } // Focus changed, which basically means that user "committed" a new recipient. // If we have at least one recipient that does not have a key, disable encryption // (unless user enabled it manually), because we want to encrypt by default, // but not by force bool encrypt = false; Q_FOREACH (auto line_, mComposerBase->recipientsEditor()->lines()) { auto line = qobject_cast(line_); // There's still a lookup job running, so wait, slotKeyForMailBoxResult() // will call us if the job returns empty key if (line->property("keyLookupJob").isValid()) { return; } const auto keyStatus = static_cast(line->property("keyStatus").toInt()); if (keyStatus == NoState) { continue; } if (!line->recipient()->isEmpty() && keyStatus != KeyOk) { setEncryption(false, false); return; } encrypt = true; } if (encrypt) { setEncryption(true, false); } } void KMComposerWin::slotRecipientLineIconClicked(MessageComposer::RecipientLineNG *line) { const auto data = line->data().dynamicCast(); if (!data->key().isNull()) { QProcess::startDetached(QStringLiteral("kleopatra"), { QStringLiteral("--query"), QString::fromLatin1(data->key().primaryFingerprint()), QStringLiteral("--parent-windowid"), QString::number(winId()) }); } } void KMComposerWin::slotRecipientAdded(MessageComposer::RecipientLineNG *line) { // User has disabled encryption, don't bother checking the key... if (!mEncryptAction->isChecked() && mEncryptAction->property("setByUser").toBool()) { return; } // Same if auto-encryption is not enabled in current identity settings if (!identity().pgpAutoEncrypt() || identity().pgpEncryptionKey().isEmpty()) { return; } if (line->recipientsCount() == 0) { return; } const auto protocol = QGpgME::openpgp(); // If we don't have gnupg we can't look for keys if (!protocol) { return; } auto recipient = line->data().dynamicCast(); // check if is an already running key lookup job and if so, cancel it // this is to prevent a slower job overwriting results of the job that we // are about to start now. const auto runningJob = line->property("keyLookupJob").value >(); if (runningJob) { disconnect(runningJob.data(), &QGpgME::KeyForMailboxJob::result, this, &KMComposerWin::slotKeyForMailBoxResult); runningJob->slotCancel(); line->setProperty("keyLookupJob", QVariant()); } QGpgME::KeyForMailboxJob *job = protocol->keyForMailboxJob(); if (!job) { line->setProperty("keyStatus", NoKey); recipient->setEncryptionAction(Kleo::Impossible); return; } QString dummy, addrSpec; if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) { addrSpec = recipient->email(); } line->setProperty("keyLookupJob", QVariant::fromValue(QPointer(job))); job->setProperty("recipient", QVariant::fromValue(recipient)); job->setProperty("line", QVariant::fromValue(QPointer(line))); connect(job, &QGpgME::KeyForMailboxJob::result, this, &KMComposerWin::slotKeyForMailBoxResult); job->start(addrSpec, true); } void KMComposerWin::slotRecipientFocusLost(MessageComposer::RecipientLineNG *line) { if (mEncryptAction->property("setByUser").toBool()) { return; } // Same if auto-encryption is not enabled in current identity settings if (!identity().pgpAutoEncrypt() || identity().pgpEncryptionKey().isEmpty()) { return; } if (line->recipientsCount() == 0) { return; } if (line->property("keyLookupJob").toBool()) { return; } if (static_cast(line->property("keyStatus").toInt()) != KeyOk) { line->setProperty("keyStatus", NoKey); setEncryption(false, false); } } void KMComposerWin::slotKeyForMailBoxResult(const GpgME::KeyListResult &, const GpgME::Key &key, const GpgME::UserID &userID) { QObject *job = sender(); Q_ASSERT(job); // Check if the encryption was explicitly disabled while the job was running if (!mEncryptAction->isChecked() && mEncryptAction->property("setByUser").toBool()) { return; } auto recipient = job->property("recipient").value(); auto line = job->property("line").value >(); if (!recipient || !line) { return; } line->setProperty("keyLookupJob", QVariant()); if (key.isNull()) { recipient->setEncryptionAction(Kleo::Impossible); // no key line->setIcon(QIcon()); line->setProperty("keyStatus", InProgress); } else { recipient->setEncryptionAction(Kleo::DoIt); recipient->setKey(key); const QIcon icon = QIcon::fromTheme(QStringLiteral("gpg")); QIcon overlay; QString tooltip; switch (userID.validity()) { case GpgME::UserID::Ultimate: case GpgME::UserID::Full: overlay = QIcon::fromTheme(QStringLiteral("emblem-favorite")); tooltip = i18n("High security encryption will be used for this recipient (the encryption key is fully trusted). " "Click the icon for details."); break; case GpgME::UserID::Marginal: overlay = QIcon::fromTheme(QStringLiteral("emblem-success")); tooltip = i18n("Medium security encryption will be used for this recipient (the encryption key is marginally trusted). " "Click the icon for details."); break; case GpgME::UserID::Never: overlay = QIcon::fromTheme(QStringLiteral("emblem-error")); tooltip = i18n("Low security encryption will be used for this recipient (the encryption key is untrusted). " "Click the icon for details."); break; case GpgME::UserID::Undefined: case GpgME::UserID::Unknown: overlay = QIcon::fromTheme(QStringLiteral("emblem-information")); tooltip = i18n("The email to this recipient will be encrypted, but the security of the encryption is unknown " "(the encryption key could not be verified). Click the icon for details."); break; } line->setProperty("keyStatus", KeyOk); line->setIcon(KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner), tooltip); slotRecipientEditorFocusChanged(); } } void KMComposerWin::slotIdentityDeleted(uint uoid) { if (mComposerBase->identityCombo()->currentIdentity() == uoid) { mIncorrectIdentityFolderWarning->identityInvalid(); } } void KMComposerWin::slotTransportRemoved(int id, const QString &name) { Q_UNUSED(name); if (mComposerBase->transportComboBox()->currentTransportId() == id) { mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); } } void KMComposerWin::slotSelectionChanged() { Q_EMIT mPluginEditorManagerInterface->textSelectionChanged(mRichTextEditorwidget->editor()->textCursor().hasSelection()); } void KMComposerWin::slotMessage(const QString &str) { KMessageBox::information(this, str, i18n("Plugin Editor Information")); } diff --git a/src/identity/identitydialog.cpp b/src/identity/identitydialog.cpp index ba3196c3f..9da30a2f2 100644 --- a/src/identity/identitydialog.cpp +++ b/src/identity/identitydialog.cpp @@ -1,1155 +1,1155 @@ /* identitydialog.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2002 Marc Mutz Copyright (C) 2014-2018 Laurent Montel 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "identitydialog.h" #include "identityeditvcarddialog.h" #include "identityaddvcarddialog.h" #include "identityinvalidfolder.h" #include "identityfolderrequester.h" #include #include #include "MessageComposer/MessageComposerSettings" #include // other KMail headers: #include "xfaceconfigurator.h" #include #include "mailcommon/folderrequester.h" #ifndef KCM_KPIMIDENTITIES_STANDALONE #include "settings/kmailsettings.h" #include "kmkernel.h" #endif #include "mailcommon/mailkernel.h" #include "job/addressvalidationjob.h" #include #include #include #include "TemplateParser/TemplatesConfiguration" #include "templatesconfiguration_kfg.h" // other kdepim headers: #include #include #include "PimCommon/AutoCorrectionLanguage" #include #include // libkleopatra: #include #include #include #include // gpgme++ #include #include #include #include #include #include using MailTransport::TransportManager; // other KDE headers: #include #include #include #include #include "kmail_debug.h" #include #include #include #include // Qt headers: #include #include #include #include #include #include #include #include #include // other headers: #include #include #include #include #include #include #include #include #include using namespace KPIM; using namespace MailTransport; using namespace MailCommon; namespace KMail { class KeySelectionCombo : public Kleo::KeySelectionCombo { Q_OBJECT public: enum KeyType { SigningKey, EncryptionKey }; KeySelectionCombo(KeyType keyType, GpgME::Protocol protocol, QWidget *parent); ~KeySelectionCombo() override; void setIdentity(const QString &name, const QString &email); void init() override; private: void onCustomItemSelected(const QVariant &type); QString mEmail; QString mName; KeyType mKeyType; GpgME::Protocol mProtocol; }; class KeyGenerationJob : public QGpgME::Job { Q_OBJECT public: KeyGenerationJob(const QString &name, const QString &email, KeySelectionCombo *parent); ~KeyGenerationJob() override; void slotCancel() override; void start(); private: void keyGenerated(const GpgME::KeyGenerationResult &result); QString mName; QString mEmail; QGpgME::Job *mJob = nullptr; }; KeyGenerationJob::KeyGenerationJob(const QString &name, const QString &email, KeySelectionCombo *parent) : QGpgME::Job(parent) , mName(name) , mEmail(email) , mJob(nullptr) { } KeyGenerationJob::~KeyGenerationJob() { } void KeyGenerationJob::slotCancel() { if (mJob) { mJob->slotCancel(); } } void KeyGenerationJob::start() { auto job = new Kleo::DefaultKeyGenerationJob(this); connect(job, &Kleo::DefaultKeyGenerationJob::result, this, &KeyGenerationJob::keyGenerated); job->start(mEmail, mName); mJob = job; } void KeyGenerationJob::keyGenerated(const GpgME::KeyGenerationResult &result) { mJob = nullptr; if (result.error()) { KMessageBox::error(qobject_cast(parent()), i18n("Error while generating new key pair: %1", QString::fromUtf8(result.error().asString())), i18n("Key Generation Error")); Q_EMIT done(); return; } KeySelectionCombo *combo = qobject_cast(parent()); combo->setDefaultKey(QLatin1String(result.fingerprint())); connect(combo, &KeySelectionCombo::keyListingFinished, this, &KeyGenerationJob::done); combo->refreshKeys(); } KeySelectionCombo::KeySelectionCombo(KeyType keyType, GpgME::Protocol protocol, QWidget *parent) : Kleo::KeySelectionCombo(parent) , mKeyType(keyType) , mProtocol(protocol) { } KeySelectionCombo::~KeySelectionCombo() { } void KeySelectionCombo::setIdentity(const QString &name, const QString &email) { mName = name; mEmail = email; setIdFilter(email); } void KeySelectionCombo::init() { Kleo::KeySelectionCombo::init(); std::shared_ptr keyFilter(new Kleo::DefaultKeyFilter); keyFilter->setIsOpenPGP(mProtocol == GpgME::OpenPGP ? Kleo::DefaultKeyFilter::Set : Kleo::DefaultKeyFilter::NotSet); if (mKeyType == SigningKey) { keyFilter->setCanSign(Kleo::DefaultKeyFilter::Set); } else { keyFilter->setCanEncrypt(Kleo::DefaultKeyFilter::Set); } keyFilter->setHasSecret(Kleo::DefaultKeyFilter::Set); setKeyFilter(keyFilter); prependCustomItem(QIcon(), i18n("No key"), QStringLiteral("no-key")); if (mProtocol == GpgME::OpenPGP) { appendCustomItem(QIcon::fromTheme(QStringLiteral("password-generate")), i18n("Generate a new key pair"), QStringLiteral("generate-new-key")); } connect(this, &KeySelectionCombo::customItemSelected, this, &KeySelectionCombo::onCustomItemSelected); } void KeySelectionCombo::onCustomItemSelected(const QVariant &type) { if (type == QLatin1String("no-key")) { return; } else if (type == QLatin1String("generate-new-key")) { auto job = new KeyGenerationJob(mName, mEmail, this); auto dlg = new Kleo::ProgressDialog(job, i18n("Generating new key pair..."), parentWidget()); dlg->setModal(true); setEnabled(false); connect(job, &KeyGenerationJob::done, this, [this]() { setEnabled(true); }); job->start(); } } IdentityDialog::IdentityDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Edit Identity")); QVBoxLayout *mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help, this); connect(buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &IdentityDialog::slotHelp); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &IdentityDialog::slotAccepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &IdentityDialog::reject); // // Tab Widget: General // int row = -1; QWidget *page = new QWidget(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); QVBoxLayout *vlay = new QVBoxLayout(page); vlay->setMargin(0); mTabWidget = new QTabWidget(page); mTabWidget->setObjectName(QStringLiteral("config-identity-tab")); vlay->addWidget(mTabWidget); QWidget *tab = new QWidget(mTabWidget); mTabWidget->addTab(tab, i18nc("@title:tab General identity settings.", "General")); QGridLayout *glay = new QGridLayout(tab); glay->setRowStretch(3, 1); glay->setColumnStretch(1, 1); // "Name" line edit and label: ++row; mNameEdit = new KLineEdit(tab); glay->addWidget(mNameEdit, row, 1); QLabel *label = new QLabel(i18n("&Your name:"), tab); label->setBuddy(mNameEdit); glay->addWidget(label, row, 0); QString msg = i18n("

Your name

" "

This field should contain your name as you would like " "it to appear in the email header that is sent out;

" "

if you leave this blank your real name will not " "appear, only the email address.

"); label->setWhatsThis(msg); mNameEdit->setWhatsThis(msg); // "Organization" line edit and label: ++row; mOrganizationEdit = new KLineEdit(tab); glay->addWidget(mOrganizationEdit, row, 1); label = new QLabel(i18n("Organi&zation:"), tab); label->setBuddy(mOrganizationEdit); glay->addWidget(label, row, 0); msg = i18n("

Organization

" "

This field should have the name of your organization " "if you would like it to be shown in the email header that " "is sent out.

" "

It is safe (and normal) to leave this blank.

"); label->setWhatsThis(msg); mOrganizationEdit->setWhatsThis(msg); // "Email Address" line edit and label: // (row 3: spacer) ++row; mEmailEdit = new KLineEdit(tab); glay->addWidget(mEmailEdit, row, 1); label = new QLabel(i18n("&Email address:"), tab); label->setBuddy(mEmailEdit); glay->addWidget(label, row, 0); msg = i18n("

Email address

" "

This field should have your full email address.

" "

This address is the primary one, used for all outgoing mail. " "If you have more than one address, either create a new identity, " "or add additional alias addresses in the field below.

" "

If you leave this blank, or get it wrong, people " "will have trouble replying to you.

"); label->setWhatsThis(msg); mEmailEdit->setWhatsThis(msg); KPIM::EmailValidator *emailValidator = new KPIM::EmailValidator(this); mEmailEdit->setValidator(emailValidator); // "Email Aliases" string text edit and label: ++row; mAliasEdit = new KEditListWidget(tab); KPIM::EmailValidator *emailValidator1 = new KPIM::EmailValidator(this); mAliasEdit->lineEdit()->setValidator(emailValidator1); glay->addWidget(mAliasEdit, row, 1); label = new QLabel(i18n("Email a&liases:"), tab); label->setBuddy(mAliasEdit); glay->addWidget(label, row, 0, Qt::AlignTop); msg = i18n("

Email aliases

" "

This field contains alias addresses that should also " "be considered as belonging to this identity (as opposed " "to representing a different identity).

" "

Example:

" "" "" "" "
Primary address:first.last@example.org
Aliases:first@example.org
last@example.org
" "

Type one alias address per line.

"); label->setToolTip(msg); mAliasEdit->setWhatsThis(msg); // // Tab Widget: Cryptography // row = -1; mCryptographyTab = tab = new QWidget(mTabWidget); mTabWidget->addTab(tab, i18n("Cryptography")); glay = new QGridLayout(tab); glay->setColumnStretch(1, 1); // "OpenPGP Signature Key" requester and label: ++row; mPGPSigningKeyRequester = new KeySelectionCombo(KeySelectionCombo::SigningKey, GpgME::OpenPGP, tab); msg = i18n("

The OpenPGP key you choose here will be used " "to digitally sign messages. You can also use GnuPG keys.

" "

You can leave this blank, but KMail will not be able " "to digitally sign emails using OpenPGP; " "normal mail functions will not be affected.

" "

You can find out more about keys at http://www.gnupg.org

"); label = new QLabel(i18n("OpenPGP signing key:"), tab); label->setBuddy(mPGPSigningKeyRequester); mPGPSigningKeyRequester->setWhatsThis(msg); label->setWhatsThis(msg); glay->addWidget(label, row, 0); glay->addWidget(mPGPSigningKeyRequester, row, 1); // "OpenPGP Encryption Key" requester and label: ++row; mPGPEncryptionKeyRequester = new KeySelectionCombo(KeySelectionCombo::EncryptionKey, GpgME::OpenPGP, tab); msg = i18n("

The OpenPGP key you choose here will be used " "to encrypt messages to yourself and for the \"Attach My Public Key\" " "feature in the composer. You can also use GnuPG keys.

" "

You can leave this blank, but KMail will not be able " "to encrypt copies of outgoing messages to you using OpenPGP; " "normal mail functions will not be affected.

" "

You can find out more about keys at http://www.gnupg.org

"); label = new QLabel(i18n("OpenPGP encryption key:"), tab); label->setBuddy(mPGPEncryptionKeyRequester); mPGPEncryptionKeyRequester->setWhatsThis(msg); label->setWhatsThis(msg); glay->addWidget(label, row, 0); glay->addWidget(mPGPEncryptionKeyRequester, row, 1); // "S/MIME Signature Key" requester and label: ++row; mSMIMESigningKeyRequester = new KeySelectionCombo(KeySelectionCombo::SigningKey, GpgME::CMS, tab); msg = i18n("

The S/MIME (X.509) certificate you choose here will be used " "to digitally sign messages.

" "

You can leave this blank, but KMail will not be able " "to digitally sign emails using S/MIME; " "normal mail functions will not be affected.

"); label = new QLabel(i18n("S/MIME signing certificate:"), tab); label->setBuddy(mSMIMESigningKeyRequester); mSMIMESigningKeyRequester->setWhatsThis(msg); label->setWhatsThis(msg); glay->addWidget(label, row, 0); glay->addWidget(mSMIMESigningKeyRequester, row, 1); const QGpgME::Protocol *smimeProtocol = QGpgME::smime(); label->setEnabled(smimeProtocol); mSMIMESigningKeyRequester->setEnabled(smimeProtocol); // "S/MIME Encryption Key" requester and label: ++row; mSMIMEEncryptionKeyRequester = new KeySelectionCombo(KeySelectionCombo::EncryptionKey, GpgME::CMS, tab); msg = i18n("

The S/MIME certificate you choose here will be used " "to encrypt messages to yourself and for the \"Attach My Certificate\" " "feature in the composer.

" "

You can leave this blank, but KMail will not be able " "to encrypt copies of outgoing messages to you using S/MIME; " "normal mail functions will not be affected.

"); label = new QLabel(i18n("S/MIME encryption certificate:"), tab); label->setBuddy(mSMIMEEncryptionKeyRequester); mSMIMEEncryptionKeyRequester->setWhatsThis(msg); label->setWhatsThis(msg); glay->addWidget(label, row, 0); glay->addWidget(mSMIMEEncryptionKeyRequester, row, 1); label->setEnabled(smimeProtocol); mSMIMEEncryptionKeyRequester->setEnabled(smimeProtocol); // "Preferred Crypto Message Format" combobox and label: ++row; mPreferredCryptoMessageFormat = new KComboBox(tab); mPreferredCryptoMessageFormat->setEditable(false); QStringList l; l << Kleo::cryptoMessageFormatToLabel(Kleo::AutoFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::InlineOpenPGPFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::OpenPGPMIMEFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::SMIMEFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::SMIMEOpaqueFormat); mPreferredCryptoMessageFormat->addItems(l); label = new QLabel(i18nc("preferred format of encrypted messages", "Preferred format:"), tab); label->setBuddy(mPreferredCryptoMessageFormat); glay->addWidget(label, row, 0); glay->addWidget(mPreferredCryptoMessageFormat, row, 1); ++row; mAutoSign = new QCheckBox(i18n("Automatically sign messages")); glay->addWidget(mAutoSign, row, 0); ++row; mAutoEncrypt = new QCheckBox(i18n("Automatically encrypt messages when possible")); glay->addWidget(mAutoEncrypt, row, 0); ++row; glay->setRowStretch(row, 1); // // Tab Widget: Advanced // row = -1; tab = new QWidget(mTabWidget); QVBoxLayout *advancedMainLayout = new QVBoxLayout(tab); mIdentityInvalidFolder = new IdentityInvalidFolder(tab); advancedMainLayout->addWidget(mIdentityInvalidFolder); mTabWidget->addTab(tab, i18nc("@title:tab Advanced identity settings.", "Advanced")); glay = new QGridLayout; advancedMainLayout->addLayout(glay); // the last (empty) row takes all the remaining space glay->setColumnStretch(1, 1); // "Reply-To Address" line edit and label: ++row; mReplyToEdit = new KPIM::AddresseeLineEdit(tab, true); mReplyToEdit->setClearButtonEnabled(true); mReplyToEdit->setObjectName(QStringLiteral("mReplyToEdit")); glay->addWidget(mReplyToEdit, row, 1); label = new QLabel(i18n("&Reply-To address:"), tab); label->setBuddy(mReplyToEdit); glay->addWidget(label, row, 0); msg = i18n("

Reply-To addresses

" "

This sets the Reply-to: header to contain a " "different email address to the normal From: " "address.

" "

This can be useful when you have a group of people " "working together in similar roles. For example, you " "might want any emails sent to have your email in the " "From: field, but any responses to go to " "a group address.

" "

If in doubt, leave this field blank.

"); label->setWhatsThis(msg); mReplyToEdit->setWhatsThis(msg); // "CC addresses" line edit and label: ++row; mCcEdit = new KPIM::AddresseeLineEdit(tab, true); mCcEdit->setClearButtonEnabled(true); mCcEdit->setObjectName(QStringLiteral("mCcEdit")); glay->addWidget(mCcEdit, row, 1); label = new QLabel(i18n("&CC addresses:"), tab); label->setBuddy(mCcEdit); glay->addWidget(label, row, 0); msg = i18n("

CC (Carbon Copy) addresses

" "

The addresses that you enter here will be added to each " "outgoing mail that is sent with this identity.

" "

This is commonly used to send a copy of each sent message to " "another account of yours.

" "

To specify more than one address, use commas to separate " "the list of CC recipients.

" "

If in doubt, leave this field blank.

"); label->setWhatsThis(msg); mCcEdit->setWhatsThis(msg); // "BCC addresses" line edit and label: ++row; mBccEdit = new KPIM::AddresseeLineEdit(tab, true); mBccEdit->setClearButtonEnabled(true); mBccEdit->setObjectName(QStringLiteral("mBccEdit")); glay->addWidget(mBccEdit, row, 1); label = new QLabel(i18n("&BCC addresses:"), tab); label->setBuddy(mBccEdit); glay->addWidget(label, row, 0); msg = i18n("

BCC (Blind Carbon Copy) addresses

" "

The addresses that you enter here will be added to each " "outgoing mail that is sent with this identity. They will not " "be visible to other recipients.

" "

This is commonly used to send a copy of each sent message to " "another account of yours.

" "

To specify more than one address, use commas to separate " "the list of BCC recipients.

" "

If in doubt, leave this field blank.

"); label->setWhatsThis(msg); mBccEdit->setWhatsThis(msg); // "Dictionary" combo box and label: ++row; mDictionaryCombo = new Sonnet::DictionaryComboBox(tab); glay->addWidget(mDictionaryCombo, row, 1); label = new QLabel(i18n("D&ictionary:"), tab); label->setBuddy(mDictionaryCombo); glay->addWidget(label, row, 0); // "Sent-mail Folder" combo box and label: ++row; mFccFolderRequester = new IdentityFolderRequester(tab); mFccFolderRequester->setShowOutbox(false); glay->addWidget(mFccFolderRequester, row, 1); mSentMailFolderCheck = new QCheckBox(i18n("Sent-mail &folder:"), tab); glay->addWidget(mSentMailFolderCheck, row, 0); connect(mSentMailFolderCheck, &QCheckBox::toggled, mFccFolderRequester, &MailCommon::FolderRequester::setEnabled); // "Drafts Folder" combo box and label: ++row; mDraftsFolderRequester = new IdentityFolderRequester(tab); mDraftsFolderRequester->setShowOutbox(false); glay->addWidget(mDraftsFolderRequester, row, 1); label = new QLabel(i18n("&Drafts folder:"), tab); label->setBuddy(mDraftsFolderRequester); glay->addWidget(label, row, 0); // "Templates Folder" combo box and label: ++row; mTemplatesFolderRequester = new IdentityFolderRequester(tab); mTemplatesFolderRequester->setShowOutbox(false); glay->addWidget(mTemplatesFolderRequester, row, 1); label = new QLabel(i18n("&Templates folder:"), tab); label->setBuddy(mTemplatesFolderRequester); glay->addWidget(label, row, 0); // "Special transport" combobox and label: ++row; mTransportCheck = new QCheckBox(i18n("Outgoing Account:"), tab); glay->addWidget(mTransportCheck, row, 0); mTransportCombo = new TransportComboBox(tab); mTransportCombo->setEnabled(false); // since !mTransportCheck->isChecked() glay->addWidget(mTransportCombo, row, 1); connect(mTransportCheck, &QCheckBox::toggled, mTransportCombo, &MailTransport::TransportComboBox::setEnabled); ++row; mAttachMyVCard = new QCheckBox(i18n("Attach my vCard to message"), tab); glay->addWidget(mAttachMyVCard, row, 0); mEditVCard = new QPushButton(i18n("Create..."), tab); connect(mEditVCard, &QPushButton::clicked, this, &IdentityDialog::slotEditVcard); glay->addWidget(mEditVCard, row, 1); ++row; mAutoCorrectionLanguage = new PimCommon::AutoCorrectionLanguage(tab); glay->addWidget(mAutoCorrectionLanguage, row, 1); label = new QLabel(i18n("Autocorrection language:"), tab); label->setBuddy(mAutoCorrectionLanguage); glay->addWidget(label, row, 0); // "default domain" input field: ++row; QHBoxLayout *hbox = new QHBoxLayout; mDefaultDomainEdit = new KLineEdit(tab); mDefaultDomainEdit->setClearButtonEnabled(true); hbox->addWidget(mDefaultDomainEdit); QToolButton *restoreDefaultDomainName = new QToolButton; restoreDefaultDomainName->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); restoreDefaultDomainName->setToolTip(i18n("Restore default domain name")); hbox->addWidget(restoreDefaultDomainName); connect(restoreDefaultDomainName, &QToolButton::clicked, this, &IdentityDialog::slotRefreshDefaultDomainName); glay->addLayout(hbox, row, 1); label = new QLabel(i18n("Defaul&t domain:"), tab); label->setBuddy(mDefaultDomainEdit); glay->addWidget(label, row, 0); // and now: add QWhatsThis: msg = i18n("

The default domain is used to complete email " "addresses that only consist of the user's name." "

"); label->setWhatsThis(msg); mDefaultDomainEdit->setWhatsThis(msg); ++row; glay->setRowStretch(row, 1); // the last row is a spacer // // Tab Widget: Templates // tab = new QWidget(mTabWidget); vlay = new QVBoxLayout(tab); QHBoxLayout *tlay = new QHBoxLayout(); vlay->addLayout(tlay); mCustom = new QCheckBox(i18n("&Use custom message templates for this identity"), tab); tlay->addWidget(mCustom, Qt::AlignLeft); mWidget = new TemplateParser::TemplatesConfiguration(tab, QStringLiteral("identity-templates")); mWidget->setEnabled(false); // Move the help label outside of the templates configuration widget, // so that the help can be read even if the widget is not enabled. tlay->addStretch(9); tlay->addWidget(mWidget->helpLabel(), Qt::AlignRight); vlay->addWidget(mWidget); QHBoxLayout *btns = new QHBoxLayout(); mCopyGlobal = new QPushButton(i18n("&Copy Global Templates"), tab); mCopyGlobal->setEnabled(false); btns->addWidget(mCopyGlobal); vlay->addLayout(btns); connect(mCustom, &QCheckBox::toggled, mWidget, &TemplateParser::TemplatesConfiguration::setEnabled); connect(mCustom, &QCheckBox::toggled, mCopyGlobal, &QPushButton::setEnabled); connect(mCopyGlobal, &QPushButton::clicked, this, &IdentityDialog::slotCopyGlobal); mTabWidget->addTab(tab, i18n("Templates")); // // Tab Widget: Signature // mSignatureConfigurator = new KIdentityManagement::SignatureConfigurator(mTabWidget); mTabWidget->addTab(mSignatureConfigurator, i18n("Signature")); // // Tab Widget: Picture // mXFaceConfigurator = new XFaceConfigurator(mTabWidget); mTabWidget->addTab(mXFaceConfigurator, i18n("Picture")); #ifndef KCM_KPIMIDENTITIES_STANDALONE resize(KMailSettings::self()->identityDialogSize()); #endif mNameEdit->setFocus(); connect(mTabWidget, &QTabWidget::currentChanged, this, &IdentityDialog::slotAboutToShow); } IdentityDialog::~IdentityDialog() { #ifndef KCM_KPIMIDENTITIES_STANDALONE KMailSettings::self()->setIdentityDialogSize(size()); #endif } void IdentityDialog::slotHelp() { PimCommon::Util::invokeHelp(QStringLiteral("kmail2/configure-identity.html")); } void IdentityDialog::slotAboutToShow(int index) { QWidget *w = mTabWidget->widget(index); if (w == mCryptographyTab) { // set the configured email address as initial query of the key // requesters: const QString name = mNameEdit->text().trimmed(); const QString email = mEmailEdit->text().trimmed(); mPGPEncryptionKeyRequester->setIdentity(name, email); mPGPSigningKeyRequester->setIdentity(name, email); mSMIMEEncryptionKeyRequester->setIdentity(name, email); mSMIMESigningKeyRequester->setIdentity(name, email); } } void IdentityDialog::slotCopyGlobal() { mWidget->loadFromGlobal(); } void IdentityDialog::slotRefreshDefaultDomainName() { mDefaultDomainEdit->setText(QHostInfo::localHostName()); } void IdentityDialog::slotAccepted() { const QStringList aliases = mAliasEdit->items(); for (const QString &alias : aliases) { if (alias.trimmed().isEmpty()) { continue; } if (!KEmailAddress::isValidSimpleAddress(alias)) { const QString errorMsg(KEmailAddress::simpleEmailAddressErrorMsg()); KMessageBox::sorry(this, errorMsg, i18n("Invalid Email Alias \"%1\"", alias)); return; } } // Validate email addresses const QString email = mEmailEdit->text().trimmed(); if (!KEmailAddress::isValidSimpleAddress(email)) { const QString errorMsg(KEmailAddress::simpleEmailAddressErrorMsg()); KMessageBox::sorry(this, errorMsg, i18n("Invalid Email Address")); return; } // Check if the 'Reply to' and 'BCC' recipients are valid const QString recipients = mReplyToEdit->text().trimmed() + QLatin1String(", ") + mBccEdit->text().trimmed() + QLatin1String(", ") + mCcEdit->text().trimmed(); AddressValidationJob *job = new AddressValidationJob(recipients, this, this); //Use default Value job->setDefaultDomain(mDefaultDomainEdit->text()); job->setProperty("email", email); connect(job, &AddressValidationJob::result, this, &IdentityDialog::slotDelayedButtonClicked); job->start(); } bool IdentityDialog::keyMatchesEmailAddress(const GpgME::Key &key, const QString &email_) { if (key.isNull()) { return true; } const QString email = email_.trimmed().toLower(); const auto uids = key.userIDs(); for (const auto &uid : uids) { QString em = QString::fromUtf8(uid.email() ? uid.email() : uid.id()); if (em.isEmpty()) { continue; } if (em[0] == QLatin1Char('<')) { em = em.mid(1, em.length() - 2); } if (em.toLower() == email) { return true; } } return false; } void IdentityDialog::slotDelayedButtonClicked(KJob *job) { const AddressValidationJob *validationJob = qobject_cast(job); // Abort if one of the recipient addresses is invalid if (!validationJob->isValid()) { return; } const QString email = validationJob->property("email").toString(); const GpgME::Key &pgpSigningKey = mPGPSigningKeyRequester->currentKey(); const GpgME::Key &pgpEncryptionKey = mPGPEncryptionKeyRequester->currentKey(); const GpgME::Key &smimeSigningKey = mSMIMESigningKeyRequester->currentKey(); const GpgME::Key &smimeEncryptionKey = mSMIMEEncryptionKeyRequester->currentKey(); QString msg; bool err = false; if (!keyMatchesEmailAddress(pgpSigningKey, email)) { msg = i18n("One of the configured OpenPGP signing keys does not contain " "any user ID with the configured email address for this " "identity (%1).\n" "This might result in warning messages on the receiving side " "when trying to verify signatures made with this configuration.", email); err = true; } else if (!keyMatchesEmailAddress(pgpEncryptionKey, email)) { msg = i18n("One of the configured OpenPGP encryption keys does not contain " "any user ID with the configured email address for this " "identity (%1).", email); err = true; } else if (!keyMatchesEmailAddress(smimeSigningKey, email)) { msg = i18n("One of the configured S/MIME signing certificates does not contain " "the configured email address for this " "identity (%1).\n" "This might result in warning messages on the receiving side " "when trying to verify signatures made with this configuration.", email); err = true; } else if (!keyMatchesEmailAddress(smimeEncryptionKey, email)) { msg = i18n("One of the configured S/MIME encryption certificates does not contain " "the configured email address for this " "identity (%1).", email); err = true; } if (err) { if (KMessageBox::warningContinueCancel(this, msg, i18n("Email Address Not Found in Key/Certificates"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn_email_not_in_certificate")) != KMessageBox::Continue) { return; } } if (mSignatureConfigurator->isSignatureEnabled() && mSignatureConfigurator->signatureType() == Signature::FromFile) { QFileInfo file(mSignatureConfigurator->filePath()); if (!file.isReadable()) { KMessageBox::error(this, i18n("The signature file is not valid")); return; } } accept(); } bool IdentityDialog::checkFolderExists(const QString &folderID) { const Akonadi::Collection folder = CommonKernel->collectionFromId(folderID.toLongLong()); return folder.isValid(); } void IdentityDialog::setIdentity(KIdentityManagement::Identity &ident) { setWindowTitle(i18n("Edit Identity \"%1\"", ident.identityName())); // "General" tab: mNameEdit->setText(ident.fullName()); mOrganizationEdit->setText(ident.organization()); mEmailEdit->setText(ident.primaryEmailAddress()); mAliasEdit->insertStringList(ident.emailAliases()); // "Cryptography" tab: mPGPSigningKeyRequester->setDefaultKey(QLatin1String(ident.pgpSigningKey())); mPGPEncryptionKeyRequester->setDefaultKey(QLatin1String(ident.pgpEncryptionKey())); mSMIMESigningKeyRequester->setDefaultKey(QLatin1String(ident.smimeSigningKey())); mSMIMEEncryptionKeyRequester->setDefaultKey(QLatin1String(ident.smimeEncryptionKey())); mPreferredCryptoMessageFormat->setCurrentIndex(format2cb( Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat()))); mAutoSign->setChecked(ident.pgpAutoSign()); mAutoEncrypt->setChecked(ident.pgpAutoEncrypt()); // "Advanced" tab: mReplyToEdit->setText(ident.replyToAddr()); mBccEdit->setText(ident.bcc()); mCcEdit->setText(ident.cc()); const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); const Transport *transport = TransportManager::self()->transportById(transportId, true); mTransportCheck->setChecked(transportId != -1); mTransportCombo->setEnabled(transportId != -1); if (transport) { mTransportCombo->setCurrentTransport(transport->id()); } mDictionaryCombo->setCurrentByDictionaryName(ident.dictionary()); mSentMailFolderCheck->setChecked(!ident.disabledFcc()); mFccFolderRequester->setEnabled(mSentMailFolderCheck->isChecked()); bool foundNoExistingFolder = false; if (ident.fcc().isEmpty() || !checkFolderExists(ident.fcc())) { foundNoExistingFolder = true; mFccFolderRequester->setIsInvalidFolder(CommonKernel->sentCollectionFolder()); } else { mFccFolderRequester->setCollection(Akonadi::Collection(ident.fcc().toLongLong())); } if (ident.drafts().isEmpty() || !checkFolderExists(ident.drafts())) { foundNoExistingFolder = true; mDraftsFolderRequester->setIsInvalidFolder(CommonKernel->draftsCollectionFolder()); } else { mDraftsFolderRequester->setCollection(Akonadi::Collection(ident.drafts().toLongLong())); } if (ident.templates().isEmpty() || !checkFolderExists(ident.templates())) { foundNoExistingFolder = true; mTemplatesFolderRequester->setIsInvalidFolder(CommonKernel->templatesCollectionFolder()); } else { mTemplatesFolderRequester->setCollection(Akonadi::Collection(ident.templates().toLongLong())); } if (foundNoExistingFolder) { mIdentityInvalidFolder->setErrorMessage(i18n("Some custom folder for identity does not exist (anymore); therefore, default folders will be used.")); } mVcardFilename = ident.vCardFile(); mAutoCorrectionLanguage->setLanguage(ident.autocorrectionLanguage()); updateVcardButton(); if (mVcardFilename.isEmpty()) { mVcardFilename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1Char('/') + ident.identityName() + QLatin1String(".vcf"); QFileInfo fileInfo(mVcardFilename); QDir().mkpath(fileInfo.absolutePath()); } else { //Convert path. const QString path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QLatin1Char('/') + ident.identityName() + QLatin1String(".vcf"); if (QFileInfo::exists(path) && (mVcardFilename != path)) { mVcardFilename = path; } } mAttachMyVCard->setChecked(ident.attachVcard()); QString defaultDomainName = ident.defaultDomainName(); if (defaultDomainName.isEmpty()) { defaultDomainName = QHostInfo::localHostName(); } mDefaultDomainEdit->setText(defaultDomainName); // "Templates" tab: uint identity = ident.uoid(); QString iid = TemplateParser::TemplatesConfiguration::configIdString(identity); TemplateParser::Templates t(iid); mCustom->setChecked(t.useCustomTemplates()); mWidget->loadFromIdentity(identity); // "Signature" tab: mSignatureConfigurator->setImageLocation(ident); mSignatureConfigurator->setSignature(ident.signature()); mXFaceConfigurator->setXFace(ident.xface()); mXFaceConfigurator->setXFaceEnabled(ident.isXFaceEnabled()); } void IdentityDialog::unregisterSpecialCollection(qint64 colId) { // ID is not enough to unregister a special collection, we need the // resource set as well. auto fetch = new Akonadi::CollectionFetchJob(Akonadi::Collection(colId), Akonadi::CollectionFetchJob::Base, this); connect(fetch, &Akonadi::CollectionFetchJob::collectionsReceived, this, [this](const Akonadi::Collection::List &cols) { - if (cols.count() != 1) { - return; - } - Akonadi::SpecialMailCollections::self()->unregisterCollection(cols.first()); - }); + if (cols.count() != 1) { + return; + } + Akonadi::SpecialMailCollections::self()->unregisterCollection(cols.first()); + }); } void IdentityDialog::updateIdentity(KIdentityManagement::Identity &ident) { // "General" tab: ident.setFullName(mNameEdit->text()); ident.setOrganization(mOrganizationEdit->text()); QString email = mEmailEdit->text().trimmed(); ident.setPrimaryEmailAddress(email); const QStringList aliases = mAliasEdit->items(); QStringList result; for (const QString &alias : aliases) { const QString aliasTrimmed = alias.trimmed(); if (aliasTrimmed.isEmpty()) { continue; } if (aliasTrimmed == email) { continue; } result.append(alias); } ident.setEmailAliases(result); // "Cryptography" tab: ident.setPGPSigningKey(mPGPSigningKeyRequester->currentKey().primaryFingerprint()); ident.setPGPEncryptionKey(mPGPEncryptionKeyRequester->currentKey().primaryFingerprint()); ident.setSMIMESigningKey(mSMIMESigningKeyRequester->currentKey().primaryFingerprint()); ident.setSMIMEEncryptionKey(mSMIMEEncryptionKeyRequester->currentKey().primaryFingerprint()); ident.setPreferredCryptoMessageFormat( QLatin1String(Kleo::cryptoMessageFormatToString(cb2format(mPreferredCryptoMessageFormat->currentIndex())))); ident.setPgpAutoSign(mAutoSign->isChecked()); ident.setPgpAutoEncrypt(mAutoEncrypt->isChecked()); // "Advanced" tab: ident.setReplyToAddr(mReplyToEdit->text()); ident.setBcc(mBccEdit->text()); ident.setCc(mCcEdit->text()); ident.setTransport(mTransportCheck->isChecked() ? QString::number(mTransportCombo->currentTransportId()) : QString()); ident.setDictionary(mDictionaryCombo->currentDictionaryName()); ident.setDisabledFcc(!mSentMailFolderCheck->isChecked()); Akonadi::Collection collection = mFccFolderRequester->collection(); if (!ident.fcc().isEmpty()) { unregisterSpecialCollection(ident.fcc().toLongLong()); } if (collection.isValid()) { ident.setFcc(QString::number(collection.id())); Akonadi::EntityDisplayAttribute *attribute = collection.attribute(Akonadi::Collection::AddIfMissing); attribute->setIconName(QStringLiteral("mail-folder-sent")); // It will also start a CollectionModifyJob Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::SentMail, collection); } else { ident.setFcc(QString()); } collection = mDraftsFolderRequester->collection(); if (!ident.drafts().isEmpty()) { unregisterSpecialCollection(ident.drafts().toLongLong()); } if (collection.isValid()) { ident.setDrafts(QString::number(collection.id())); Akonadi::EntityDisplayAttribute *attribute = collection.attribute(Akonadi::Collection::AddIfMissing); attribute->setIconName(QStringLiteral("document-properties")); // It will also start a CollectionModifyJob Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::Drafts, collection); } else { ident.setDrafts(QString()); } collection = mTemplatesFolderRequester->collection(); if (ident.templates().isEmpty()) { unregisterSpecialCollection(ident.templates().toLongLong()); } if (collection.isValid()) { ident.setTemplates(QString::number(collection.id())); Akonadi::EntityDisplayAttribute *attribute = collection.attribute(Akonadi::Collection::AddIfMissing); attribute->setIconName(QStringLiteral("document-new")); // It will also start a CollectionModifyJob Akonadi::SpecialMailCollections::self()->registerCollection(Akonadi::SpecialMailCollections::Templates, collection); new Akonadi::CollectionModifyJob(collection); } else { ident.setTemplates(QString()); } ident.setVCardFile(mVcardFilename); ident.setAutocorrectionLanguage(mAutoCorrectionLanguage->language()); updateVcardButton(); ident.setAttachVcard(mAttachMyVCard->isChecked()); //Add default ? ident.setDefaultDomainName(mDefaultDomainEdit->text()); // "Templates" tab: uint identity = ident.uoid(); QString iid = TemplateParser::TemplatesConfiguration::configIdString(identity); TemplateParser::Templates t(iid); qCDebug(KMAIL_LOG) << "use custom templates for identity" << identity << ":" << mCustom->isChecked(); t.setUseCustomTemplates(mCustom->isChecked()); t.save(); mWidget->saveToIdentity(identity); // "Signature" tab: ident.setSignature(mSignatureConfigurator->signature()); ident.setXFace(mXFaceConfigurator->xface()); ident.setXFaceEnabled(mXFaceConfigurator->isXFaceEnabled()); } void IdentityDialog::slotEditVcard() { if (QFileInfo::exists(mVcardFilename)) { editVcard(mVcardFilename); } else { if (!MailCommon::Kernel::self()->kernelIsRegistered()) { return; } KIdentityManagement::IdentityManager *manager = KernelIf->identityManager(); QPointer dlg = new IdentityAddVcardDialog(manager->shadowIdentities(), this); if (dlg->exec()) { IdentityAddVcardDialog::DuplicateMode mode = dlg->duplicateMode(); switch (mode) { case IdentityAddVcardDialog::Empty: editVcard(mVcardFilename); break; case IdentityAddVcardDialog::ExistingEntry: { KIdentityManagement::Identity ident = manager->modifyIdentityForName(dlg->duplicateVcardFromIdentity()); const QString filename = ident.vCardFile(); if (!filename.isEmpty()) { QFile::copy(filename, mVcardFilename); } editVcard(mVcardFilename); break; } case IdentityAddVcardDialog::FromExistingVCard: { const QString filename = dlg->existingVCard().path(); if (!filename.isEmpty()) { mVcardFilename = filename; } editVcard(mVcardFilename); break; } } } delete dlg; } } void IdentityDialog::editVcard(const QString &filename) { QPointer dlg = new IdentityEditVcardDialog(filename, this); connect(dlg.data(), &IdentityEditVcardDialog::vcardRemoved, this, &IdentityDialog::slotVCardRemoved); if (dlg->exec()) { mVcardFilename = dlg->saveVcard(); } updateVcardButton(); delete dlg; } void IdentityDialog::slotVCardRemoved() { mVcardFilename.clear(); } void IdentityDialog::updateVcardButton() { if (mVcardFilename.isEmpty() || !QFileInfo::exists(mVcardFilename)) { mEditVCard->setText(i18n("Create...")); } else { mEditVCard->setText(i18n("Edit...")); } } } #include "identitydialog.moc" diff --git a/src/job/dndfromarkjob.cpp b/src/job/dndfromarkjob.cpp index 621155f7a..ff660167a 100644 --- a/src/job/dndfromarkjob.cpp +++ b/src/job/dndfromarkjob.cpp @@ -1,82 +1,81 @@ /* Copyright (C) 2018 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dndfromarkjob.h" #include "editor/kmcomposerwin.h" #include "kmail_debug.h" #include #include #include #include #include #include DndFromArkJob::DndFromArkJob(QObject *parent) : QObject(parent) { - } bool DndFromArkJob::dndFromArk(const QMimeData *source) { - if (source->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && - source->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { + if (source->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) + && source->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { return true; } return false; } bool DndFromArkJob::extract(const QMimeData *source) { if (dndFromArk(source)) { if (!mComposerWin) { qCWarning(KMAIL_LOG) << "mComposerWin is null, it's a bug"; deleteLater(); return false; } const QString remoteDBusClient = QString::fromLatin1(source->data(QStringLiteral("application/x-kde-ark-dndextract-service"))); const QString remoteDBusPath = QString::fromLatin1(source->data(QStringLiteral("application/x-kde-ark-dndextract-path"))); const QString tmpPath = QDir::tempPath() + QLatin1String("/attachments_ark"); QDir().mkpath(tmpPath); QTemporaryDir *linkDir = new QTemporaryDir(tmpPath); const QString arkPath = linkDir->path(); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments({arkPath}); QDBusConnection::sessionBus().call(message); QDir dir(arkPath); QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Files); for (int i = 0; i < list.size(); ++i) { mComposerWin->addAttachment(QUrl::fromLocalFile(list.at(i)), QString()); } delete linkDir; deleteLater(); return true; } deleteLater(); return false; } void DndFromArkJob::setComposerWin(KMComposerWin *composerWin) { mComposerWin = composerWin; } diff --git a/src/job/dndfromarkjob.h b/src/job/dndfromarkjob.h index 43393241d..bfae4ed9a 100644 --- a/src/job/dndfromarkjob.h +++ b/src/job/dndfromarkjob.h @@ -1,41 +1,40 @@ /* Copyright (C) 2018 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DNDFROMARKJOB_H #define DNDFROMARKJOB_H #include - class QMimeData; class KMComposerWin; class DndFromArkJob : public QObject { Q_OBJECT public: explicit DndFromArkJob(QObject *parent = nullptr); static bool dndFromArk(const QMimeData *source); bool extract(const QMimeData *source); void setComposerWin(KMComposerWin *composerWin); private: KMComposerWin *mComposerWin = nullptr; }; #endif // DNDFROMARKJOB_H diff --git a/src/kmcommands.cpp b/src/kmcommands.cpp index 0617c90b3..9dcecd6ba 100644 --- a/src/kmcommands.cpp +++ b/src/kmcommands.cpp @@ -1,1873 +1,1873 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (C) 2013-2018 Laurent Montel 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 */ // // This file implements various "command" classes. These command classes // are based on the command design pattern. // // Historically various operations were implemented as slots of KMMainWin. // This proved inadequate as KMail has multiple top level windows // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may // benefit from using these operations. It is desirable that these // classes can operate without depending on or altering the state of // a KMMainWin, in fact it is possible no KMMainWin object even exists. // // Now these operations have been rewritten as KMCommand based classes, // making them independent of KMMainWin. // // The base command class KMCommand is async, which is a difference // from the conventional command pattern. As normal derived classes implement // the execute method, but client classes call start() instead of // calling execute() directly. start() initiates async operations, // and on completion of these operations calls execute() and then deletes // the command. (So the client must not construct commands on the stack). // // The type of async operation supported by KMCommand is retrieval // of messages from an IMAP server. #include "kmcommands.h" #include "widgets/collectionpane.h" #include "kmreadermainwin.h" #include "secondarywindow.h" #include "util.h" #include "settings/kmailsettings.h" #include "kmail_debug.h" #include "job/createreplymessagejob.h" #include "job/createforwardmessagejob.h" #include "editor/composer.h" #include "kmmainwidget.h" #include "undostack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_CURSOR #include #endif #include #include #include #include #include #include #include #include // KIO headers #include #include #include #include #include #include #include #include #include #include using KMail::SecondaryWindow; using MailTransport::TransportManager; using MessageComposer::MessageFactoryNG; using KPIM::ProgressManager; using KPIM::ProgressItem; using namespace KMime; using namespace MailCommon; /// Helper to sanely show an error message for a job static void showJobError(KJob *job) { assert(job); // we can be called from the KJob::kill, where we are no longer a KIO::Job // so better safe than sorry KIO::Job *kiojob = qobject_cast(job); if (kiojob && kiojob->uiDelegate()) { kiojob->uiDelegate()->showErrorMessage(); } else { qCWarning(KMAIL_LOG) << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString(); } } KMCommand::KMCommand(QWidget *parent) : mCountMsgs(0) , mResult(Undefined) , mDeletesItself(false) , mEmitsCompletedItself(false) , mParent(parent) { } KMCommand::KMCommand(QWidget *parent, const Akonadi::Item &msg) : mCountMsgs(0) , mResult(Undefined) , mDeletesItself(false) , mEmitsCompletedItself(false) , mParent(parent) { if (msg.isValid() || msg.hasPayload()) { mMsgList.append(msg); } } KMCommand::KMCommand(QWidget *parent, const Akonadi::Item::List &msgList) : mCountMsgs(0) , mResult(Undefined) , mDeletesItself(false) , mEmitsCompletedItself(false) , mParent(parent) { mMsgList = msgList; } KMCommand::~KMCommand() { } KMCommand::Result KMCommand::result() const { if (mResult == Undefined) { qCDebug(KMAIL_LOG) << "mResult is Undefined"; } return mResult; } const Akonadi::Item::List KMCommand::retrievedMsgs() const { return mRetrievedMsgs; } Akonadi::Item KMCommand::retrievedMessage() const { if (mRetrievedMsgs.isEmpty()) { return Akonadi::Item(); } return *(mRetrievedMsgs.begin()); } QWidget *KMCommand::parentWidget() const { return mParent; } bool KMCommand::deletesItself() const { return mDeletesItself; } void KMCommand::setDeletesItself(bool deletesItself) { mDeletesItself = deletesItself; } bool KMCommand::emitsCompletedItself() const { return mEmitsCompletedItself; } void KMCommand::setEmitsCompletedItself(bool emitsCompletedItself) { mEmitsCompletedItself = emitsCompletedItself; } void KMCommand::setResult(KMCommand::Result result) { mResult = result; } int KMCommand::mCountJobs = 0; void KMCommand::start() { connect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer); if (mMsgList.isEmpty()) { Q_EMIT messagesTransfered(OK); return; } // Special case of operating on message that isn't in a folder const Akonadi::Item mb = mMsgList.first(); if ((mMsgList.count() == 1) && MessageComposer::Util::isStandaloneMessage(mb)) { mRetrievedMsgs.append(mMsgList.takeFirst()); Q_EMIT messagesTransfered(OK); return; } // we can only retrieve items with a valid id for (const Akonadi::Item &item : qAsConst(mMsgList)) { if (!item.isValid()) { Q_EMIT messagesTransfered(Failed); return; } } // transfer the selected messages first transferSelectedMsgs(); } void KMCommand::slotPostTransfer(KMCommand::Result result) { disconnect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer); if (result == OK) { result = execute(); } mResult = result; if (!emitsCompletedItself()) { Q_EMIT completed(this); } if (!deletesItself()) { deleteLater(); } } Akonadi::ItemFetchJob *KMCommand::createFetchJob(const Akonadi::Item::List &items) { return new Akonadi::ItemFetchJob(items, this); } void KMCommand::transferSelectedMsgs() { // make sure no other transfer is active if (KMCommand::mCountJobs > 0) { Q_EMIT messagesTransfered(Failed); return; } bool complete = true; KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); mCountMsgs = mMsgList.count(); uint totalSize = 0; // the QProgressDialog for the user-feedback. Only enable it if it's needed. // For some commands like KMSetStatusCommand it's not needed. Note, that // for some reason the QProgressDialog eats the MouseReleaseEvent (if a // command is executed after the MousePressEvent), cf. bug #71761. if (mCountMsgs > 0) { mProgressDialog = new QProgressDialog(mParent); mProgressDialog.data()->setWindowTitle(i18n("Please wait")); mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count())); mProgressDialog.data()->setModal(true); mProgressDialog.data()->setMinimumDuration(1000); } // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever if (!mFetchScope.isEmpty()) { complete = false; ++KMCommand::mCountJobs; Akonadi::ItemFetchJob *fetch = createFetchJob(mMsgList); mFetchScope.fetchAttribute< MailCommon::MDNStateAttribute >(); fetch->setFetchScope(mFetchScope); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, &KMCommand::slotMsgTransfered); connect(fetch, &Akonadi::ItemFetchJob::result, this, &KMCommand::slotJobFinished); } else { // no need to fetch anything if (!mMsgList.isEmpty()) { mRetrievedMsgs = mMsgList; } } if (complete) { delete mProgressDialog.data(); mProgressDialog.clear(); Q_EMIT messagesTransfered(OK); } else { // wait for the transfer and tell the progressBar the necessary steps if (mProgressDialog.data()) { connect(mProgressDialog.data(), &QProgressDialog::canceled, this, &KMCommand::slotTransferCancelled); mProgressDialog.data()->setMaximum(totalSize); } } } void KMCommand::slotMsgTransfered(const Akonadi::Item::List &msgs) { if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) { Q_EMIT messagesTransfered(Canceled); return; } // save the complete messages mRetrievedMsgs.append(msgs); } void KMCommand::slotJobFinished() { // the job is finished (with / without error) KMCommand::mCountJobs--; if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) { return; } if (mCountMsgs > mRetrievedMsgs.count()) { // the message wasn't retrieved before => error if (mProgressDialog.data()) { mProgressDialog.data()->hide(); } slotTransferCancelled(); return; } // update the progressbar if (mProgressDialog.data()) { mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", KMCommand::mCountJobs)); } if (KMCommand::mCountJobs == 0) { // all done delete mProgressDialog.data(); mProgressDialog.clear(); Q_EMIT messagesTransfered(OK); } } void KMCommand::slotTransferCancelled() { KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); Q_EMIT messagesTransfered(Canceled); } KMMailtoComposeCommand::KMMailtoComposeCommand(const QUrl &url, const Akonadi::Item &msg) : mUrl(url) , mMessage(msg) { } KMCommand::Result KMMailtoComposeCommand::execute() { KMime::Message::Ptr msg(new KMime::Message); uint id = 0; if (mMessage.isValid() && mMessage.parentCollection().isValid()) { QSharedPointer fd = FolderSettings::forCollection(mMessage.parentCollection(), false); id = fd->identity(); } MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id); msg->contentType()->setCharset("utf-8"); msg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8"); KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id); win->setFocusToSubject(); win->show(); return OK; } KMMailtoReplyCommand::KMMailtoReplyCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg, const QString &selection) : KMCommand(parent, msg) , mUrl(url) , mSelection(selection) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMMailtoReplyCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } CreateReplyMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mSelection = mSelection; settings.mUrl = mUrl; settings.m_replyStrategy = MessageComposer::ReplyNone; CreateReplyMessageJob *job = new CreateReplyMessageJob; job->setSettings(settings); job->start(); return OK; } KMMailtoForwardCommand::KMMailtoForwardCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg) : KMCommand(parent, msg) , mUrl(url) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMMailtoForwardCommand::execute() { //TODO : consider factoring createForward into this method. Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } CreateForwardMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mUrl = mUrl; CreateForwardMessageJob *job = new CreateForwardMessageJob; job->setSettings(settings); job->start(); return OK; } KMAddBookmarksCommand::KMAddBookmarksCommand(const QUrl &url, QWidget *parent) : KMCommand(parent) , mUrl(url) { } KMCommand::Result KMAddBookmarksCommand::execute() { const QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/konqueror/bookmarks.xml"); QFileInfo fileInfo(filename); QDir().mkpath(fileInfo.absolutePath()); KBookmarkManager *bookManager = KBookmarkManager::managerForFile(filename, QStringLiteral("konqueror")); KBookmarkGroup group = bookManager->root(); group.addBookmark(mUrl.path(), QUrl(mUrl), QString()); if (bookManager->save()) { bookManager->emitChanged(group); } return OK; } KMUrlSaveCommand::KMUrlSaveCommand(const QUrl &url, QWidget *parent) : KMCommand(parent) , mUrl(url) { } KMCommand::Result KMUrlSaveCommand::execute() { if (mUrl.isEmpty()) { return OK; } QString recentDirClass; QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///OpenMessage")), recentDirClass); startUrl.setPath(startUrl.path() + QLatin1Char('/') + mUrl.fileName()); const QUrl saveUrl = QFileDialog::getSaveFileUrl(parentWidget(), i18n("Save To File"), startUrl); if (saveUrl.isEmpty()) { return Canceled; } if (!recentDirClass.isEmpty()) { KRecentDirs::add(recentDirClass, saveUrl.path()); } bool fileExists = false; if (saveUrl.isLocalFile()) { fileExists = QFile::exists(saveUrl.toLocalFile()); } else { auto job = KIO::stat(saveUrl, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(job, parentWidget()); fileExists = job->exec(); } if (fileExists) { if (KMessageBox::warningContinueCancel(nullptr, xi18nc("@info", "File %1 exists.Do you want to replace it?", saveUrl.toDisplayString()), i18n("Save to File"), KGuiItem(i18n("&Replace"))) != KMessageBox::Continue) { return Canceled; } } KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite); connect(job, &KIO::Job::result, this, &KMUrlSaveCommand::slotUrlSaveResult); setEmitsCompletedItself(true); return OK; } void KMUrlSaveCommand::slotUrlSaveResult(KJob *job) { if (job->error()) { showJobError(job); setResult(Failed); } else { setResult(OK); } Q_EMIT completed(this); } KMEditMessageCommand::KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg) : KMCommand(parent) , mMessage(msg) { } KMCommand::Result KMEditMessageCommand::execute() { if (!mMessage) { return Failed; } KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage); win->setMessage(mMessage, lastSign, lastEncrypt, false, true); win->show(); win->setModified(true); return OK; } KMEditItemCommand::KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource) : KMCommand(parent, msg) , mDeleteFromSource(deleteFromSource) { fetchScope().fetchFullPayload(true); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMEditItemCommand::~KMEditItemCommand() { } KMCommand::Result KMEditItemCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.parentCollection().isValid()) { return Failed; } KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } if (mDeleteFromSource) { setDeletesItself(true); Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(item); connect(job, &KIO::Job::result, this, &KMEditItemCommand::slotDeleteItem); } KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); win->setMessage(msg, lastSign, lastEncrypt, false, true); win->setFolder(item.parentCollection()); const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if (transportAttribute) { win->setCurrentTransport(transportAttribute->transportId()); } else { int transportId = -1; if (auto hrd = msg->headerByType("X-KMail-Transport")) { transportId = hrd->asUnicodeString().toInt(); } if (transportId != -1) { win->setCurrentTransport(transportId); } } if (auto hdr = msg->replyTo(false)) { const QString replyTo = hdr->asUnicodeString(); win->setCurrentReplyTo(replyTo); } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) { win->setFcc(QString::number(sentAttribute->moveToCollection().id())); } win->show(); if (mDeleteFromSource) { win->setModified(true); } return OK; } void KMEditItemCommand::slotDeleteItem(KJob *job) { if (job->error()) { showJobError(job); setResult(Failed); } else { setResult(OK); } Q_EMIT completed(this); deleteLater(); } KMUseTemplateCommand::KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMUseTemplateCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.parentCollection().isValid() || !CommonKernel->folderIsTemplates(item.parentCollection()) ) { return Failed; } KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } KMime::Message::Ptr newMsg(new KMime::Message); newMsg->setContent(msg->encodedContent()); newMsg->parse(); // these fields need to be regenerated for the new message newMsg->removeHeader(); newMsg->removeHeader(); KMail::Composer *win = KMail::makeComposer(); win->setMessage(newMsg, false, false, false, true); win->show(); return OK; } KMSaveMsgCommand::KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList) : KMCommand(parent, msgList) { if (msgList.empty()) { return; } fetchScope().fetchFullPayload(true); // ### unless we call the corresponding KMCommand ctor, this has no effect } KMCommand::Result KMSaveMsgCommand::execute() { if (!MessageViewer::Util::saveMessageInMbox(retrievedMsgs(), parentWidget())) { return Failed; } return OK; } //----------------------------------------------------------------------------- KMOpenMsgCommand::KMOpenMsgCommand(QWidget *parent, const QUrl &url, const QString &encoding, KMMainWidget *main) : KMCommand(parent) , mUrl(url) , mJob(nullptr) , mEncoding(encoding) , mMainWidget(main) { qCDebug(KMAIL_LOG) << "url :" << url; } KMCommand::Result KMOpenMsgCommand::execute() { if (mUrl.isEmpty()) { mUrl = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Open Message"), QUrl(), QStringLiteral("%1 (*.mbox)").arg(i18n("Message")) ); } if (mUrl.isEmpty()) { return Canceled; } if (mMainWidget) { mMainWidget->addRecentFile(mUrl); } setDeletesItself(true); mJob = KIO::get(mUrl, KIO::NoReload, KIO::HideProgressInfo); connect(mJob, &KIO::TransferJob::data, this, &KMOpenMsgCommand::slotDataArrived); connect(mJob, &KJob::result, this, &KMOpenMsgCommand::slotResult); setEmitsCompletedItself(true); return OK; } void KMOpenMsgCommand::slotDataArrived(KIO::Job *, const QByteArray &data) { if (data.isEmpty()) { return; } mMsgString.append(QString::fromLatin1(data.data())); } void KMOpenMsgCommand::doesNotContainMessage() { KMessageBox::sorry(parentWidget(), i18n("The file does not contain a message.")); setResult(Failed); Q_EMIT completed(this); // Emulate closing of a secondary window so that KMail exits in case it // was started with the --view command line option. Otherwise an // invisible KMail would keep running. SecondaryWindow *win = new SecondaryWindow(); win->close(); win->deleteLater(); deleteLater(); } void KMOpenMsgCommand::slotResult(KJob *job) { if (job->error()) { // handle errors showJobError(job); setResult(Failed); } else { if (mMsgString.isEmpty()) { qCDebug(KMAIL_LOG) << " Message not found. There is a problem"; doesNotContainMessage(); return; } int startOfMessage = 0; if (mMsgString.startsWith(QLatin1String("From "))) { startOfMessage = mMsgString.indexOf(QLatin1Char('\n')); if (startOfMessage == -1) { doesNotContainMessage(); return; } startOfMessage += 1; // the message starts after the '\n' } // check for multiple messages in the file bool multipleMessages = true; int endOfMessage = mMsgString.indexOf(QLatin1String("\nFrom ")); if (endOfMessage == -1) { endOfMessage = mMsgString.length(); multipleMessages = false; } KMime::Message *msg = new KMime::Message; msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage).toUtf8())); msg->parse(); if (!msg->hasContent()) { delete msg; msg = nullptr; doesNotContainMessage(); return; } KMReaderMainWin *win = new KMReaderMainWin(); KMime::Message::Ptr mMsg(msg); win->showMessage(mEncoding, mMsg); win->show(); if (multipleMessages) { KMessageBox::information(win, i18n("The file contains multiple messages. " "Only the first message is shown.")); } setResult(OK); } Q_EMIT completed(this); deleteLater(); } //----------------------------------------------------------------------------- KMReplyCommand::KMReplyCommand(QWidget *parent, const Akonadi::Item &msg, MessageComposer::ReplyStrategy replyStrategy, const QString &selection, bool noquote, const QString &templateName) : KMCommand(parent, msg) , mSelection(selection) , mTemplate(templateName) , m_replyStrategy(replyStrategy) , mNoQuote(noquote) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMReplyCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } CreateReplyMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mSelection = mSelection; settings.m_replyStrategy = m_replyStrategy; settings.mTemplate = mTemplate; settings.mNoQuote = mNoQuote; CreateReplyMessageJob *job = new CreateReplyMessageJob; job->setSettings(settings); job->start(); return OK; } KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, const QString &templateName, const QString &selection) : KMCommand(parent, msgList) , mIdentity(identity) , mTemplate(templateName) , mSelection(selection) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, const QString &templateName, const QString &selection) : KMCommand(parent, msg) , mIdentity(identity) , mTemplate(templateName) , mSelection(selection) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item &item) { KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif CreateForwardMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mIdentity = mIdentity; settings.mTemplate = mTemplate; settings.mSelection = mSelection; CreateForwardMessageJob *job = new CreateForwardMessageJob; job->setSettings(settings); job->start(); return OK; } KMCommand::Result KMForwardCommand::execute() { Akonadi::Item::List msgList = retrievedMsgs(); if (msgList.count() >= 2) { // ask if they want a mime digest forward int answer = KMessageBox::questionYesNoCancel( parentWidget(), i18n("Do you want to forward the selected messages as " "attachments in one message (as a MIME digest) or as " "individual messages?"), QString(), KGuiItem(i18n("Send As Digest")), KGuiItem(i18n("Send Individually"))); if (answer == KMessageBox::Yes) { Akonadi::Item firstItem(msgList.first()); MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), CommonKernel->collectionFromId(firstItem.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem)); QPair< KMime::Message::Ptr, KMime::Content * > fwdMsg = factory.createForwardDigestMIME(msgList); KMail::Composer *win = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity); win->addAttach(fwdMsg.second); win->show(); return OK; } else if (answer == KMessageBox::No) { // NO MIME DIGEST, Multiple forward Akonadi::Item::List::const_iterator it; Akonadi::Item::List::const_iterator end(msgList.constEnd()); for (it = msgList.constBegin(); it != end; ++it) { if (createComposer(*it) == Failed) { return Failed; } } return OK; } else { // user cancelled return OK; } } // forward a single message at most. Akonadi::Item item = msgList.first(); if (createComposer(item) == Failed) { return Failed; } return OK; } KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, KMail::Composer *win) : KMCommand(parent, msgList) , mIdentity(identity) , mWin(QPointer(win)) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, KMail::Composer *win) : KMCommand(parent, msg) , mIdentity(identity) , mWin(QPointer< KMail::Composer >(win)) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMForwardAttachedCommand::execute() { Akonadi::Item::List msgList = retrievedMsgs(); Akonadi::Item firstItem(msgList.first()); MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), CommonKernel->collectionFromId(firstItem.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem)); QPair< KMime::Message::Ptr, QList< KMime::Content * > > fwdMsg = factory.createAttachedForward(msgList); if (!mWin) { mWin = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity); } for (KMime::Content *attach : qAsConst(fwdMsg.second)) { mWin->addAttach(attach); } mWin->show(); return OK; } KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item::List &msgList) : KMCommand(parent, msgList) { fetchScope().fetchFullPayload(true); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { fetchScope().fetchFullPayload(true); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMRedirectCommand::execute() { const MailCommon::RedirectDialog::SendMode sendMode = MessageComposer::MessageComposerSettings::self()->sendImmediate() ? MailCommon::RedirectDialog::SendNow : MailCommon::RedirectDialog::SendLater; QScopedPointer dlg(new MailCommon::RedirectDialog(sendMode, parentWidget())); dlg->setObjectName(QStringLiteral("redirect")); if (dlg->exec() == QDialog::Rejected || !dlg) { return Failed; } if (!TransportManager::self()->showTransportCreationDialog(parentWidget(), TransportManager::IfNoTransportExists)) { return Failed; } //TODO use sendlateragent here too. const MessageComposer::MessageSender::SendMethod method = (dlg->sendMode() == MailCommon::RedirectDialog::SendNow) ? MessageComposer::MessageSender::SendImmediate : MessageComposer::MessageSender::SendLater; const int identity = dlg->identity(); int transportId = dlg->transportId(); const QString to = dlg->to(); const QString cc = dlg->cc(); const QString bcc = dlg->bcc(); const Akonadi::Item::List lstItems = retrievedMsgs(); for (const Akonadi::Item &item : lstItems) { const KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(item)); if (transportId == -1) { const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if (transportAttribute) { transportId = transportAttribute->transportId(); const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId); if (!transport) { transportId = -1; } } } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); QString fcc; if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) { fcc = QString::number(sentAttribute->moveToCollection().id()); } const KMime::Message::Ptr newMsg = factory.createRedirect(to, cc, bcc, transportId, fcc, identity); if (!newMsg) { return Failed; } MessageStatus status; status.setStatusFromFlags(item.flags()); if (!status.isRead()) { FilterAction::sendMDN(item, KMime::MDN::Dispatched); } if (!kmkernel->msgSender()->send(newMsg, method)) { qCDebug(KMAIL_LOG) << "KMRedirectCommand: could not redirect message (sending failed)"; return Failed; // error: couldn't send } } return OK; } KMPrintCommand::KMPrintCommand(QWidget *parent, const KMPrintCommandInfo &commandInfo) : KMCommand(parent, commandInfo.mMsg) , mPrintCommandInfo(commandInfo) { fetchScope().fetchFullPayload(true); if (MessageCore::MessageCoreSettings::useDefaultFonts()) { mPrintCommandInfo.mOverrideFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); } else { mPrintCommandInfo.mOverrideFont = MessageViewer::MessageViewerSettings::self()->printFont(); } } KMCommand::Result KMPrintCommand::execute() { KMReaderWin *printerWin = new KMReaderWin(nullptr, kmkernel->mainWin(), nullptr); printerWin->setPrinting(true); printerWin->readConfig(); printerWin->setPrintElementBackground(MessageViewer::MessageViewerSettings::self()->printBackgroundColorImages()); if (mPrintCommandInfo.mHeaderStylePlugin) { printerWin->viewer()->setPluginName(mPrintCommandInfo.mHeaderStylePlugin->name()); } printerWin->setDisplayFormatMessageOverwrite(mPrintCommandInfo.mFormat); printerWin->setHtmlLoadExtOverride(mPrintCommandInfo.mHtmlLoadExtOverride); printerWin->setUseFixedFont(mPrintCommandInfo.mUseFixedFont); printerWin->setOverrideEncoding(mPrintCommandInfo.mEncoding); printerWin->cssHelper()->setPrintFont(mPrintCommandInfo.mOverrideFont); printerWin->setDecryptMessageOverwrite(true); if (mPrintCommandInfo.mAttachmentStrategy) { printerWin->setAttachmentStrategy(mPrintCommandInfo.mAttachmentStrategy); } printerWin->viewer()->setShowSignatureDetails(mPrintCommandInfo.mShowSignatureDetails); printerWin->viewer()->setShowEncryptionDetails(mPrintCommandInfo.mShowEncryptionDetails); if (mPrintCommandInfo.mPrintPreview) { printerWin->viewer()->printPreviewMessage(retrievedMessage()); } else { printerWin->viewer()->printMessage(retrievedMessage()); } return OK; } KMSetStatusCommand::KMSetStatusCommand(const MessageStatus &status, const Akonadi::Item::List &items, bool invert) : KMCommand(nullptr, items) , mStatus(status) , mInvertMark(invert) { setDeletesItself(true); } KMCommand::Result KMSetStatusCommand::execute() { bool parentStatus = false; // Toggle actions on threads toggle the whole thread // depending on the state of the parent. if (mInvertMark) { const Akonadi::Item first = retrievedMsgs().first(); MessageStatus pStatus; pStatus.setStatusFromFlags(first.flags()); if (pStatus & mStatus) { parentStatus = true; } else { parentStatus = false; } } Akonadi::Item::List itemsToModify; const Akonadi::Item::List lstItems = retrievedMsgs(); for (const Akonadi::Item &it : lstItems) { if (mInvertMark) { //qCDebug(KMAIL_LOG)<<" item ::"<disableRevisionCheck(); modifyJob->setIgnorePayload(true); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetStatusCommand::slotModifyItemDone); } return OK; } void KMSetStatusCommand::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMSetTagCommand::KMSetTagCommand(const Akonadi::Tag::List &tags, const Akonadi::Item::List &item, SetTagMode mode) : mTags(tags) , mItem(item) , mMode(mode) { setDeletesItself(true); } KMCommand::Result KMSetTagCommand::execute() { for (const Akonadi::Tag &tag : qAsConst(mTags)) { if (!tag.isValid()) { Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(tag, this); connect(createJob, &Akonadi::TagCreateJob::result, this, &KMSetTagCommand::slotModifyItemDone); } else { mCreatedTags << tag; } } if (mCreatedTags.size() == mTags.size()) { setTags(); } else { deleteLater(); } return OK; } void KMSetTagCommand::setTags() { Akonadi::Item::List itemsToModify; itemsToModify.reserve(mItem.count()); for (const Akonadi::Item &i : qAsConst(mItem)) { Akonadi::Item item(i); if (mMode == CleanExistingAndAddNew) { //WorkAround. ClearTags doesn't work. const Akonadi::Tag::List lstTags = item.tags(); for (const Akonadi::Tag &tag : lstTags) { item.clearTag(tag); } //item.clearTags(); } if (mMode == KMSetTagCommand::Toggle) { for (const Akonadi::Tag &tag : qAsConst(mCreatedTags)) { if (item.hasTag(tag)) { item.clearTag(tag); } else { item.setTag(tag); } } } else { if (!mCreatedTags.isEmpty()) { item.setTags(mCreatedTags); } } itemsToModify << item; } Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this); modifyJob->disableRevisionCheck(); modifyJob->setIgnorePayload(true); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetTagCommand::slotModifyItemDone); if (!mCreatedTags.isEmpty()) { KConfigGroup tag(KMKernel::self()->config(), "MessageListView"); const QString oldTagList = tag.readEntry("TagSelected"); QStringList lst = oldTagList.split(QLatin1Char(',')); for (const Akonadi::Tag &tag : qAsConst(mCreatedTags)) { const QString url = tag.url().url(); if (!lst.contains(url)) { lst.append(url); } } tag.writeEntry("TagSelected", lst); KMKernel::self()->updatePaneTagComboBox(); } } void KMSetTagCommand::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMFilterActionCommand::KMFilterActionCommand(QWidget *parent, const QVector &msgListId, const QString &filterId) : KMCommand(parent) , mMsgListId(msgListId) , mFilterId(filterId) { } KMCommand::Result KMFilterActionCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif int msgCount = 0; const int msgCountToFilter = mMsgListId.count(); ProgressItem *progressItem = ProgressManager::createProgressItem( - QLatin1String("filter") + ProgressManager::getUniqueID(), - i18n("Filtering messages"), QString(), true, KPIM::ProgressItem::Unknown); + QLatin1String("filter") + ProgressManager::getUniqueID(), + i18n("Filtering messages"), QString(), true, KPIM::ProgressItem::Unknown); progressItem->setTotalItems(msgCountToFilter); for (const qlonglong &id : qAsConst(mMsgListId)) { int diff = msgCountToFilter - ++msgCount; if (diff < 10 || !(msgCount % 10) || msgCount <= 10) { progressItem->updateProgress(); const QString statusMsg = i18n("Filtering message %1 of %2", msgCount, msgCountToFilter); KPIM::BroadcastStatus::instance()->setStatusMsg(statusMsg); qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 50); } MailCommon::FilterManager::instance()->filter(Akonadi::Item(id), mFilterId, QString()); progressItem->incCompletedItems(); } progressItem->setComplete(); progressItem = nullptr; return OK; } KMMetaFilterActionCommand::KMMetaFilterActionCommand(const QString &filterId, KMMainWidget *main) : QObject(main) , mFilterId(filterId) , mMainWidget(main) { } void KMMetaFilterActionCommand::start() { KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId(), mFilterId); filterCommand->start(); } KMMailingListFilterCommand::KMMailingListFilterCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { } KMCommand::Result KMMailingListFilterCommand::execute() { QByteArray name; QString value; Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } if (!MailingList::name(msg, name, value).isEmpty()) { FilterIf->openFilterDialog(false); FilterIf->createFilter(name, value); return OK; } else { return Failed; } } KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList) : KMCommand(nullptr, msgList) , mDestFolder(destFolder) { } KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg) : KMCommand(nullptr, msg) , mDestFolder(destFolder) { } KMCommand::Result KMCopyCommand::execute() { setDeletesItself(true); Akonadi::Item::List listItem = retrievedMsgs(); Akonadi::ItemCopyJob *job = new Akonadi::ItemCopyJob(listItem, Akonadi::Collection(mDestFolder.id()), this); connect(job, &KIO::Job::result, this, &KMCopyCommand::slotCopyResult); return OK; } void KMCopyCommand::slotCopyResult(KJob *job) { if (job->error()) { // handle errors showJobError(job); setResult(Failed); } qobject_cast(job); Q_EMIT completed(this); deleteLater(); } KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList) : KMCommand(nullptr, msgList) , mDestFolder(destFolder) { fetchScope().fetchAllAttributes(); fetchScope().fetchFullPayload(); } KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg) : KMCopyDecryptedCommand(destFolder, Akonadi::Item::List{ msg }) { } KMCommand::Result KMCopyDecryptedCommand::execute() { setDeletesItself(true); const auto items = retrievedMsgs(); for (const auto &item : items) { // Decrypt if (!item.hasPayload()) { continue; } const auto msg = item.payload(); bool wasEncrypted; auto decMsg = MailCommon::CryptoUtils::decryptMessage(msg, wasEncrypted); if (!wasEncrypted) { decMsg = msg; } Akonadi::Item decItem; decItem.setMimeType(KMime::Message::mimeType()); decItem.setPayload(decMsg); auto job = new Akonadi::ItemCreateJob(decItem, mDestFolder, this); connect(job, &Akonadi::Job::result, this, &KMCopyDecryptedCommand::slotAppendResult); mPendingJobs << job; } if (mPendingJobs.isEmpty()) { Q_EMIT completed(this); deleteLater(); } return KMCommand::OK; } void KMCopyDecryptedCommand::slotAppendResult(KJob *job) { mPendingJobs.removeOne(job); if (mPendingJobs.isEmpty()) { Q_EMIT completed(this); deleteLater(); } } KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref) : KMCommand(nullptr, msgList) , mDestFolder(destFolder) , mProgressItem(nullptr) , mRef(ref) { fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref) : KMCommand(nullptr, msg) , mDestFolder(destFolder) , mProgressItem(nullptr) , mRef(ref) { fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } void KMMoveCommand::slotMoveResult(KJob *job) { if (job->error()) { // handle errors showJobError(job); completeMove(Failed); } else { completeMove(OK); } } KMCommand::Result KMMoveCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif setEmitsCompletedItself(true); setDeletesItself(true); Akonadi::Item::List retrievedList = retrievedMsgs(); if (!retrievedList.isEmpty()) { if (mDestFolder.isValid()) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(retrievedList, mDestFolder, this); connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult); // group by source folder for undo std::sort(retrievedList.begin(), retrievedList.end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) { return lhs.storageCollectionId() < rhs.storageCollectionId(); }); Akonadi::Collection parent; int undoId = -1; for (const Akonadi::Item &item : qAsConst(retrievedList)) { if (item.storageCollectionId() <= 0) { continue; } if (parent.id() != item.storageCollectionId()) { parent = Akonadi::Collection(item.storageCollectionId()); undoId = kmkernel->undoStack()->newUndoAction(parent, mDestFolder); } kmkernel->undoStack()->addMsgToAction(undoId, item); } } else { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(retrievedList, this); connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult); } } else { deleteLater(); return Failed; } // TODO set SSL state according to source and destfolder connection? Q_ASSERT(!mProgressItem); mProgressItem = ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(), mDestFolder.isValid() ? i18n("Moving messages") : i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown); mProgressItem->setUsesBusyIndicator(true); connect(mProgressItem, &ProgressItem::progressItemCanceled, this, &KMMoveCommand::slotMoveCanceled); return OK; } void KMMoveCommand::completeMove(Result result) { if (mProgressItem) { mProgressItem->setComplete(); mProgressItem = nullptr; } setResult(result); Q_EMIT moveDone(this); Q_EMIT completed(this); deleteLater(); } void KMMoveCommand::slotMoveCanceled() { completeMove(Canceled); } KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref) : KMCommand() , mRef(ref) { // When trashing items from a virtual collection, they may each have a different // trash folder, so we need to handle it here carefully if (srcFolder.isVirtual()) { QHash cache; for (const auto &msg : msgList) { auto cacheIt = cache.find(msg.storageCollectionId()); if (cacheIt == cache.end()) { cacheIt = cache.insert(msg.storageCollectionId(), findTrashFolder(CommonKernel->collectionFromId(msg.storageCollectionId()))); } auto trashIt = mTrashFolders.find(*cacheIt); if (trashIt == mTrashFolders.end()) { trashIt = mTrashFolders.insert(*cacheIt, {}); } trashIt->push_back(msg); } } else { mTrashFolders.insert(findTrashFolder(srcFolder), msgList); } } KMTrashMsgCommand::TrashOperation KMTrashMsgCommand::operation() const { if (!mPendingMoves.isEmpty() && !mPendingDeletes.isEmpty()) { return Both; } else if (!mPendingMoves.isEmpty()) { return MoveToTrash; } else if (!mPendingDeletes.isEmpty()) { return Delete; } else { if (mTrashFolders.size() == 1) { if (mTrashFolders.begin().key().isValid()) { return MoveToTrash; } else { return Delete; } } else { return Unknown; } } } KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref) : KMTrashMsgCommand(findTrashFolder(srcFolder), Akonadi::Item::List{msg}, ref) -{} +{ +} Akonadi::Collection KMTrashMsgCommand::findTrashFolder(const Akonadi::Collection &folder) { Akonadi::Collection col = CommonKernel->trashCollectionFromResource(folder); if (!col.isValid()) { col = CommonKernel->trashCollectionFolder(); } if (folder != col) { return col; } return Akonadi::Collection(); } KMCommand::Result KMTrashMsgCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif setEmitsCompletedItself(true); setDeletesItself(true); for (auto trashIt = mTrashFolders.begin(), end = mTrashFolders.end(); trashIt != end; ++trashIt) { const auto trash = trashIt.key(); if (trash.isValid()) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(*trashIt, trash, this); connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotMoveResult); mPendingMoves.push_back(job); // group by source folder for undo std::sort(trashIt->begin(), trashIt->end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) { return lhs.storageCollectionId() < rhs.storageCollectionId(); }); Akonadi::Collection parent; int undoId = -1; for (const Akonadi::Item &item : qAsConst(*trashIt)) { if (item.storageCollectionId() <= 0) { continue; } if (parent.id() != item.storageCollectionId()) { parent = Akonadi::Collection(item.storageCollectionId()); undoId = kmkernel->undoStack()->newUndoAction(parent, trash); } kmkernel->undoStack()->addMsgToAction(undoId, item); } } else { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(*trashIt, this); connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotDeleteResult); mPendingDeletes.push_back(job); } } if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) { deleteLater(); return Failed; } // TODO set SSL state according to source and destfolder connection? if (!mPendingMoves.isEmpty()) { Q_ASSERT(!mMoveProgress); mMoveProgress = ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(), - i18n("Moving messages"), QString(), true, KPIM::ProgressItem::Unknown); + i18n("Moving messages"), QString(), true, KPIM::ProgressItem::Unknown); mMoveProgress->setUsesBusyIndicator(true); connect(mMoveProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled); } if (!mPendingDeletes.isEmpty()) { Q_ASSERT(!mDeleteProgress); mDeleteProgress = ProgressManager::createProgressItem(QLatin1String("delete") + ProgressManager::getUniqueID(), - i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown); + i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown); mDeleteProgress->setUsesBusyIndicator(true); connect(mMoveProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled); } return OK; } void KMTrashMsgCommand::slotMoveResult(KJob *job) { mPendingMoves.removeOne(job); if (job->error()) { // handle errors showJobError(job); completeMove(Failed); } else if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) { completeMove(OK); } } -void KMTrashMsgCommand::slotDeleteResult(KJob* job) +void KMTrashMsgCommand::slotDeleteResult(KJob *job) { mPendingDeletes.removeOne(job); if (job->error()) { showJobError(job); completeMove(Failed); } else if (mPendingDeletes.isEmpty() && mPendingMoves.isEmpty()) { completeMove(OK); } } void KMTrashMsgCommand::slotMoveCanceled() { completeMove(Canceled); } void KMTrashMsgCommand::completeMove(KMCommand::Result result) { if (result == Failed) { for (auto job : mPendingMoves) { job->kill(); } for (auto job : mPendingDeletes) { job->kill(); } } if (mDeleteProgress) { mDeleteProgress->setComplete(); mDeleteProgress = nullptr; } if (mMoveProgress) { mMoveProgress->setComplete(); mMoveProgress = nullptr; } setResult(result); Q_EMIT moveDone(this); Q_EMIT completed(this); deleteLater(); } - KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer) : KMCommand(parent, msg) , mViewer(viewer) { fetchScope().fetchFullPayload(true); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs) : KMCommand(parent, msgs) , mViewer(nullptr) { fetchScope().fetchFullPayload(true); } KMCommand::Result KMSaveAttachmentsCommand::execute() { KMime::Content::List contentsToSave; const Akonadi::Item::List lstItems = retrievedMsgs(); for (const Akonadi::Item &item : lstItems) { if (item.hasPayload()) { contentsToSave += item.payload()->attachments(); } else { qCWarning(KMAIL_LOG) << "Retrieved item has no payload? Ignoring for saving the attachments"; } } QList urlList; if (MessageViewer::Util::saveAttachments(contentsToSave, parentWidget(), urlList)) { if (mViewer) { mViewer->showOpenAttachmentFolderWidget(urlList); } return OK; } return Failed; } KMResendMessageCommand::KMResendMessageCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMResendMessageCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(item)); KMime::Message::Ptr newMsg = factory.createResend(); newMsg->contentType()->setCharset(MimeTreeParser::NodeHelper::charset(msg.data())); KMail::Composer *win = KMail::makeComposer(); if (auto hdr = msg->replyTo(false)) { const QString replyTo = hdr->asUnicodeString(); win->setCurrentReplyTo(replyTo); } bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); win->setMessage(newMsg, lastSign, lastEncrypt, false, true); win->show(); return OK; } KMShareImageCommand::KMShareImageCommand(const QUrl &url, QWidget *parent) : KMCommand(parent) , mUrl(url) { } KMCommand::Result KMShareImageCommand::execute() { KMime::Message::Ptr msg(new KMime::Message); uint id = 0; MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id); msg->contentType()->setCharset("utf-8"); KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id); win->setFocusToSubject(); win->addAttachment(mUrl, i18n("Image")); win->show(); return OK; } KMFetchMessageCommand::KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item, MessageViewer::Viewer *viewer, KMReaderMainWin *win) : KMCommand(parent, item) , mViewer(viewer) , mReaderMainWin(win) { // Workaround KMCommand::transferSelectedMsgs() expecting non-empty fetchscope fetchScope().fetchFullPayload(true); } Akonadi::ItemFetchJob *KMFetchMessageCommand::createFetchJob(const Akonadi::Item::List &items) { Q_ASSERT(items.size() == 1); Akonadi::ItemFetchJob *fetch = mViewer->createFetchJob(items.first()); fetchScope() = fetch->fetchScope(); return fetch; } KMCommand::Result KMFetchMessageCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.hasPayload()) { return Failed; } mItem = item; return OK; } KMReaderMainWin *KMFetchMessageCommand::readerMainWin() const { return mReaderMainWin; } Akonadi::Item KMFetchMessageCommand::item() const { return mItem; } diff --git a/src/kmcommands.h b/src/kmcommands.h index 75e89a452..e05b3b842 100644 --- a/src/kmcommands.h +++ b/src/kmcommands.h @@ -1,648 +1,648 @@ // #ifndef KMCommands_h #define KMCommands_h #include #include "MessageList/View" #include "mailcommon/searchpattern.h" #include "messageviewer/viewer.h" #include #include #include #include #include #include #include #include #include namespace Akonadi { class Tag; } namespace KPIM { class ProgressItem; } using Akonadi::MessageStatus; class QProgressDialog; class KMMainWidget; class KMReaderMainWin; template class QSharedPointer; namespace MessageViewer { class HeaderStyle; class AttachmentStrategy; } namespace KIO { class Job; } namespace KMail { class Composer; } typedef QMap PartNodeMessageMap; /// Small helper structure which encapsulates the KMMessage created when creating a reply, and class KMCommand : public QObject { Q_OBJECT public: enum Result { Undefined, OK, Canceled, Failed }; // Trival constructor, don't retrieve any messages explicit KMCommand(QWidget *parent = nullptr); KMCommand(QWidget *parent, const Akonadi::Item &); // Retrieve all messages in msgList when start is called. KMCommand(QWidget *parent, const Akonadi::Item::List &msgList); // Retrieve the single message msgBase when start is called. ~KMCommand() override; /** Returns the result of the command. Only call this method from the slot connected to completed(). */ Result result() const; public Q_SLOTS: // Retrieve messages then calls execute void start(); Q_SIGNALS: /// @param result The status of the command. void messagesTransfered(KMCommand::Result result); /// Emitted when the command has completed. void completed(KMCommand *command); protected: virtual Akonadi::ItemFetchJob *createFetchJob(const Akonadi::Item::List &items); /** Allows to configure how much data should be retrieved of the messages. */ Akonadi::ItemFetchScope &fetchScope() { return mFetchScope; } // Returns list of messages retrieved const Akonadi::Item::List retrievedMsgs() const; // Returns the single message retrieved Akonadi::Item retrievedMessage() const; // Returns the parent widget QWidget *parentWidget() const; bool deletesItself() const; /** Specify whether the subclass takes care of the deletion of the object. By default the base class will delete the object. @param deletesItself true if the subclass takes care of deletion, false if the base class should take care of deletion */ void setDeletesItself(bool deletesItself); bool emitsCompletedItself() const; /** Specify whether the subclass takes care of emitting the completed() signal. By default the base class will Q_EMIT this signal. @param emitsCompletedItself true if the subclass emits the completed signal, false if the base class should Q_EMIT the signal */ void setEmitsCompletedItself(bool emitsCompletedItself); /** Use this to set the result of the command. @param result The result of the command. */ void setResult(Result result); private: Q_DISABLE_COPY(KMCommand) // execute should be implemented by derived classes virtual Result execute() = 0; /** transfers the list of (imap)-messages * this is a necessary preparation for e.g. forwarding */ void transferSelectedMsgs(); private Q_SLOTS: void slotPostTransfer(KMCommand::Result result); /** the msg has been transferred */ void slotMsgTransfered(const Akonadi::Item::List &msgs); /** the KMImapJob is finished */ void slotJobFinished(); /** the transfer was canceled */ void slotTransferCancelled(); protected: Akonadi::Item::List mRetrievedMsgs; private: // ProgressDialog for transferring messages QPointer mProgressDialog; //Currently only one async command allowed at a time static int mCountJobs; int mCountMsgs; Result mResult; bool mDeletesItself : 1; bool mEmitsCompletedItself : 1; QWidget *mParent = nullptr; Akonadi::Item::List mMsgList; Akonadi::ItemFetchScope mFetchScope; }; class KMMailtoComposeCommand : public KMCommand { Q_OBJECT public: explicit KMMailtoComposeCommand(const QUrl &url, const Akonadi::Item &msg = Akonadi::Item()); private: Result execute() override; QUrl mUrl; Akonadi::Item mMessage; }; class KMMailtoReplyCommand : public KMCommand { Q_OBJECT public: KMMailtoReplyCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg, const QString &selection); private: Result execute() override; QUrl mUrl; QString mSelection; }; class KMMailtoForwardCommand : public KMCommand { Q_OBJECT public: KMMailtoForwardCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg); private: Result execute() override; QUrl mUrl; }; class KMAddBookmarksCommand : public KMCommand { Q_OBJECT public: KMAddBookmarksCommand(const QUrl &url, QWidget *parent); private: Result execute() override; QUrl mUrl; }; class KMUrlSaveCommand : public KMCommand { Q_OBJECT public: KMUrlSaveCommand(const QUrl &url, QWidget *parent); private Q_SLOTS: void slotUrlSaveResult(KJob *job); private: Result execute() override; QUrl mUrl; }; class KMEditItemCommand : public KMCommand { Q_OBJECT public: explicit KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource = true); ~KMEditItemCommand(); private Q_SLOTS: void slotDeleteItem(KJob *job); private: Result execute() override; bool mDeleteFromSource = false; }; class KMEditMessageCommand : public KMCommand { Q_OBJECT public: explicit KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg); private: Result execute() override; KMime::Message::Ptr mMessage; }; class KMUseTemplateCommand : public KMCommand { Q_OBJECT public: KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg); private: Result execute() override; }; class KMSaveMsgCommand : public KMCommand { Q_OBJECT public: KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList); private: Result execute() override; }; class KMOpenMsgCommand : public KMCommand { Q_OBJECT public: explicit KMOpenMsgCommand(QWidget *parent, const QUrl &url = QUrl(), const QString &encoding = QString(), KMMainWidget *main = nullptr); private: Result execute() override; private Q_SLOTS: void slotDataArrived(KIO::Job *job, const QByteArray &data); void slotResult(KJob *job); private: void doesNotContainMessage(); static const int MAX_CHUNK_SIZE = 64 * 1024; QUrl mUrl; QString mMsgString; KIO::TransferJob *mJob = nullptr; const QString mEncoding; KMMainWidget *mMainWidget = nullptr; }; class KMSaveAttachmentsCommand : public KMCommand { Q_OBJECT public: /** Use this to save all attachments of the given message. @param parent The parent widget of the command used for message boxes. @param msg The message of which the attachments should be saved. */ KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer); /** Use this to save all attachments of the given messages. @param parent The parent widget of the command used for message boxes. @param msgs The messages of which the attachments should be saved. */ KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs); private: Result execute() override; MessageViewer::Viewer *mViewer = nullptr; }; class KMReplyCommand : public KMCommand { Q_OBJECT public: KMReplyCommand(QWidget *parent, const Akonadi::Item &msg, MessageComposer::ReplyStrategy replyStrategy, const QString &selection = QString(), bool noquote = false, const QString &templateName = QString()); private: Result execute() override; private: QString mSelection; QString mTemplate; MessageComposer::ReplyStrategy m_replyStrategy; bool mNoQuote = false; }; class KMForwardCommand : public KMCommand { Q_OBJECT public: KMForwardCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity = 0, const QString &templateName = QString(), const QString &selection = QString()); KMForwardCommand(QWidget *parent, const Akonadi::Item &msg, uint identity = 0, const QString &templateName = QString(), const QString &selection = QString()); private: KMCommand::Result createComposer(const Akonadi::Item &item); Result execute() override; private: uint mIdentity; QString mTemplate; QString mSelection; }; class KMForwardAttachedCommand : public KMCommand { Q_OBJECT public: KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity = 0, KMail::Composer *win = nullptr); KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item &msg, uint identity = 0, KMail::Composer *win = nullptr); private: Result execute() override; uint mIdentity; QPointer mWin; }; class KMRedirectCommand : public KMCommand { Q_OBJECT public: KMRedirectCommand(QWidget *parent, const Akonadi::Item &msg); KMRedirectCommand(QWidget *parent, const Akonadi::Item::List &msgList); private: Result execute() override; }; struct KMPrintCommandInfo { Akonadi::Item mMsg; QFont mOverrideFont; QString mEncoding; MessageViewer::Viewer::DisplayFormatMessage mFormat = MessageViewer::Viewer::UseGlobalSetting; const MessageViewer::AttachmentStrategy *mAttachmentStrategy = nullptr; MessageViewer::HeaderStylePlugin *mHeaderStylePlugin = nullptr; bool mHtmlLoadExtOverride = false; bool mUseFixedFont = false; bool mPrintPreview = false; bool mShowSignatureDetails = false; bool mShowEncryptionDetails = false; }; class KMPrintCommand : public KMCommand { Q_OBJECT public: KMPrintCommand(QWidget *parent, const KMPrintCommandInfo &commandInfo); private: Result execute() override; KMPrintCommandInfo mPrintCommandInfo; }; class KMSetStatusCommand : public KMCommand { Q_OBJECT public: // Serial numbers KMSetStatusCommand(const MessageStatus &status, const Akonadi::Item::List &items, bool invert = false); protected Q_SLOTS: void slotModifyItemDone(KJob *job); private: Result execute() override; MessageStatus mStatus; bool mInvertMark = false; }; /** This command is used to set or toggle a tag for a list of messages. If toggle is true then the tag is deleted if it is already applied. */ class KMSetTagCommand : public KMCommand { Q_OBJECT public: enum SetTagMode { AddIfNotExisting, Toggle, CleanExistingAndAddNew }; KMSetTagCommand(const Akonadi::Tag::List &tags, const Akonadi::Item::List &item, SetTagMode mode = AddIfNotExisting); protected Q_SLOTS: void slotModifyItemDone(KJob *job); private: Result execute() override; void setTags(); Akonadi::Tag::List mTags; Akonadi::Tag::List mCreatedTags; Akonadi::Item::List mItem; SetTagMode mMode; }; /* This command is used to apply a single filter (AKA ad-hoc filter) to a set of messages */ class KMFilterActionCommand : public KMCommand { Q_OBJECT public: KMFilterActionCommand(QWidget *parent, const QVector &msgListId, const QString &filterId); private: Result execute() override; QVector mMsgListId; QString mFilterId; }; class KMMetaFilterActionCommand : public QObject { Q_OBJECT public: KMMetaFilterActionCommand(const QString &filterId, KMMainWidget *main); public Q_SLOTS: void start(); private: QString mFilterId; KMMainWidget *mMainWidget = nullptr; }; class KMMailingListFilterCommand : public KMCommand { Q_OBJECT public: KMMailingListFilterCommand(QWidget *parent, const Akonadi::Item &msg); private: Result execute() override; }; class KMCopyCommand : public KMCommand { Q_OBJECT public: KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList); KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg); protected Q_SLOTS: void slotCopyResult(KJob *job); private: Result execute() override; Akonadi::Collection mDestFolder; }; class KMCopyDecryptedCommand : public KMCommand { Q_OBJECT public: KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList); KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg); protected Q_SLOTS: void slotAppendResult(KJob *job); private: Result execute() override; Akonadi::Collection mDestFolder; QList mPendingJobs; }; class KMMoveCommand : public KMCommand { Q_OBJECT public: KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref); KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref = MessageList::Core::MessageItemSetReference()); Akonadi::Collection destFolder() const { return mDestFolder; } MessageList::Core::MessageItemSetReference refSet() const { return mRef; } public Q_SLOTS: void slotMoveCanceled(); void slotMoveResult(KJob *job); protected: void setDestFolder(const Akonadi::Collection &folder) { mDestFolder = folder; } Q_SIGNALS: void moveDone(KMMoveCommand *); private: Result execute() override; void completeMove(Result result); Akonadi::Collection mDestFolder; KPIM::ProgressItem *mProgressItem = nullptr; MessageList::Core::MessageItemSetReference mRef; }; class KMTrashMsgCommand final : public KMCommand { Q_OBJECT public: enum TrashOperation { Unknown, MoveToTrash, Delete, Both }; KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref); KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref); MessageList::Core::MessageItemSetReference refSet() const { return mRef; } TrashOperation operation() const; public Q_SLOTS: void slotMoveCanceled(); private Q_SLOTS: void slotMoveResult(KJob *job); void slotDeleteResult(KJob *job); Q_SIGNALS: void moveDone(KMTrashMsgCommand *); private: Result execute() override; void completeMove(Result result); static Akonadi::Collection findTrashFolder(const Akonadi::Collection &srcFolder); QMap mTrashFolders; KPIM::ProgressItem *mMoveProgress = nullptr; KPIM::ProgressItem *mDeleteProgress = nullptr; MessageList::Core::MessageItemSetReference mRef; - QList mPendingMoves; - QList mPendingDeletes; + QList mPendingMoves; + QList mPendingDeletes; }; class KMResendMessageCommand : public KMCommand { Q_OBJECT public: explicit KMResendMessageCommand(QWidget *parent, const Akonadi::Item &msg = Akonadi::Item()); private: Result execute() override; }; class KMShareImageCommand : public KMCommand { Q_OBJECT public: explicit KMShareImageCommand(const QUrl &url, QWidget *parent); private: Result execute() override; QUrl mUrl; }; class KMFetchMessageCommand : public KMCommand { Q_OBJECT public: explicit KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item, MessageViewer::Viewer *viewer, KMReaderMainWin *win = nullptr); Akonadi::Item item() const; KMReaderMainWin *readerMainWin() const; private: Akonadi::ItemFetchJob *createFetchJob(const Akonadi::Item::List &items) override; Result execute() override; Akonadi::Item mItem; MessageViewer::Viewer *mViewer = nullptr; KMReaderMainWin *mReaderMainWin = nullptr; }; #endif /*KMCommands_h*/ diff --git a/src/kmmainwidget.cpp b/src/kmmainwidget.cpp index 59bcca410..d00a9e54b 100644 --- a/src/kmmainwidget.cpp +++ b/src/kmmainwidget.cpp @@ -1,4851 +1,4850 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2009-2018 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 "kmmainwidget.h" #include "kmreadermainwin.h" #include "job/composenewmessagejob.h" #include "editor/composer.h" #include "searchdialog/searchwindow.h" #include "widgets/vacationscriptindicatorwidget.h" #include "widgets/zoomlabelwidget.h" #include "undostack.h" #include "kmcommands.h" #include "kmmainwin.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" #include #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/FavoriteCollectionOrderProxyModel" #include "mailcommonsettings_base.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 #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 MessageViewer::AttachmentStrategy; Q_GLOBAL_STATIC(KMMainWidget::PtrList, theMainWidgetList) //----------------------------------------------------------------------------- KMMainWidget::KMMainWidget(QWidget *parent, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, KSharedConfig::Ptr config) : QWidget(parent) , mManageShowCollectionProperties(new ManageShowCollectionProperties(this, this)) { 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, &KSieveUi::VacationManager::updateVacationScriptStatus, this, QOverload::of(&KMMainWidget::updateVacationScriptStatus)); 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()); mCurrentStatusBar = mainWin ? mainWin->statusBar() : nullptr; mVacationScriptIndicator = new KMail::VacationScriptIndicatorWidget(mCurrentStatusBar); mVacationScriptIndicator->hide(); mZoomLabelIndicator = new ZoomLabelWidget(mCurrentStatusBar); if (mMsgView) { setZoomChanged(mMsgView->viewer()->webViewZoomFactor()); } 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); setupUnifiedMailboxChecker(); } 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 {}; } // Connected to the currentChanged signals from the folderTreeView and favorites view. void KMMainWidget::slotFolderChanged(const Akonadi::Collection &collection) { - if (mCurrentCollection == collection) + if (mCurrentCollection == collection) { return; + } folderSelected(collection); if (collection.cachePolicy().syncOnDemand()) { AgentManager::self()->synchronizeCollection(collection, false); } mMsgActions->setCurrentMessage(Akonadi::Item()); Q_EMIT captionChangeRequest(MailCommon::Util::fullCollectionPath(collection)); } // Called by slotFolderChanged (no particular reason for this method to be split out) void KMMainWidget::folderSelected(const Akonadi::Collection &col) { 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->setHtmlLoadExtDefault(mFolderHtmlLoadExtPreference); } if (!mCurrentFolderSettings->isValid() && (mMessagePane->count() < 2)) { slotIntro(); } else { if (mMessagePane->isHidden()) { mMessagePane->show(); } } // 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()) { QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(KMKernel::self()->entityTreeModel(), mCurrentCollection); mMessagePane->setCurrentFolder(mCurrentCollection, idx, 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, static_cast(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(), QOverload::of(&EntityTreeView::currentChanged), this, &KMMainWidget::slotFolderChanged); 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); connect(mMessagePane, &MessageList::Pane::forceLostFocus, this, &KMMainWidget::slotSetFocusToViewer); // // 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); connect(mMsgView->viewer(), &MessageViewer::Viewer::showStatusBarMessage, this, &KMMainWidget::setShowStatusBarMessage); connect(mMsgView->viewer(), &MessageViewer::Viewer::zoomChanged, this, &KMMainWidget::setZoomChanged); 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, QOverload::of(&EntityListView::currentChanged), this, &KMMainWidget::slotFolderChanged); connect(mFavoriteCollectionsView, &FavoriteCollectionWidget::newTabRequested, this, &KMMainWidget::slotCreateNewTab); mFavoritesModel = new Akonadi::FavoriteCollectionsModel( mFolderTreeWidget->folderTreeWidgetProxyModel(), KMKernel::self()->config()->group("FavoriteCollections"), mFavoriteCollectionsView); auto *orderProxy = new MailCommon::FavoriteCollectionOrderProxyModel(this); orderProxy->setOrderConfig(KMKernel::self()->config()->group("FavoriteCollectionsOrder")); orderProxy->setSourceModel(mFavoritesModel); orderProxy->sort(0, Qt::AscendingOrder); mFavoriteCollectionsView->setModel(orderProxy); 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(), QOverload &>::of(&ChangeRecorder::collectionChanged), this, &KMMainWidget::slotCollectionChanged); 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(qint64 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::showSearchDialog() { 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()) { qCWarning(KMAIL_LOG) << "Template folder not found"; 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("edit-delete-shred"))) != 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. */ deleteSelectedMessages(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().constFirst(), 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->setHtmlLoadExtDefault(mFolderHtmlLoadExtPreference); 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()); } //----------------------------------------------------------------------------- // 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) { const int selectedMessageCount = selectMsg.count(); 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.
", selectedMessageCount ), selectedMessageCount > 1 ? i18n("Delete Messages") : i18n("Delete Message"), KStandardGuiItem::del(), KStandardGuiItem::cancel() ); 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() { deleteSelectedMessages(true && !KMailSettings::self()->deleteMessageWithoutConfirmation()); } 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::deleteSelectedMessages(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, &KMTrashMsgCommand::moveDone, this, &KMMainWidget::slotTrashMessagesCompleted); command->start(); switch (command->operation()) { case KMTrashMsgCommand::MoveToTrash: showMessageActivities(i18n("Moving messages to trash...")); break; case KMTrashMsgCommand::Delete: showMessageActivities(i18n("Deleting messages...")); break; case KMTrashMsgCommand::Both: case KMTrashMsgCommand::Unknown: showMessageActivities(i18n("Deleting and moving messages to trash...")); break; } } void KMMainWidget::slotTrashMessagesCompleted(KMTrashMsgCommand *command) { Q_ASSERT(command); mMessagePane->markMessageItemsAsAboutToBeRemoved(command->refSet(), false); mMessagePane->deletePersistentSet(command->refSet()); if (command->result() == KMCommand::OK) { switch (command->operation()) { case KMTrashMsgCommand::MoveToTrash: showMessageActivities(i18n("Messages moved to trash successfully.")); break; case KMTrashMsgCommand::Delete: showMessageActivities(i18n("Messages deleted successfully.")); break; case KMTrashMsgCommand::Both: case KMTrashMsgCommand::Unknown: showMessageActivities(i18n("Messages moved to trash or deleted successfully")); break; } } else if (command->result() == KMCommand::Failed) { switch (command->operation()) { case KMTrashMsgCommand::MoveToTrash: showMessageActivities(i18n("Moving messages to trash failed.")); break; case KMTrashMsgCommand::Delete: showMessageActivities(i18n("Deleting messages failed.")); break; case KMTrashMsgCommand::Both: case KMTrashMsgCommand::Unknown: showMessageActivities(i18n("Deleting or moving messages to trash failed.")); break; } } else { switch (command->operation()) { case KMTrashMsgCommand::MoveToTrash: showMessageActivities(i18n("Moving messages to trash canceled.")); break; case KMTrashMsgCommand::Delete: showMessageActivities(i18n("Deleting messages canceled.")); break; case KMTrashMsgCommand::Both: case KMTrashMsgCommand::Unknown: showMessageActivities(i18n("Deleting or moving messages to trash canceled.")); break; } } // 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); // call it explicitly in case the collection is filtered out in the foldertreeview } } 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 (kmkernel->allowToDebug()) { QPointer mSieveDebugDialog = new KSieveUi::SieveDebugDialog(mSievePasswordProvider, this); mSieveDebugDialog->exec(); delete mSieveDebugDialog; } } 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()) { if (!kmkernel->msgSender()->sendQueued()) { KMessageBox::error(this, i18n("Impossible to send email"), i18n("Send Email")); } } } //----------------------------------------------------------------------------- void KMMainWidget::slotSendQueuedVia(MailTransport::Transport *transport) { if (transport) { if (kmkernel->msgSender()) { if (!kmkernel->msgSender()->sendQueued(transport->id())) { KMessageBox::error(this, i18n("Impossible to send email"), i18n("Send Email")); } } } } //----------------------------------------------------------------------------- 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) << "slotItemsFetchedForActivation result:" << result; return; } KMFetchMessageCommand *fetchCmd = qobject_cast(command); const Item msg = fetchCmd->item(); KMReaderMainWin *win = fetchCmd->readerMainWin(); if (!win) { win = new KMReaderMainWin(mFolderDisplayFormatPreference, mFolderHtmlLoadExtPreference); } win->viewer()->setWebViewZoomFactor(mMsgView->viewer()->webViewZoomFactor()); 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(mMsgActions->newMessageFromTemplateAction()); } else { menu.addAction(mMsgActions->replyMenu()); menu.addAction(mMsgActions->forwardMenu()); } if (parentCol.isValid() && CommonKernel->folderIsSentMailFolder(parentCol)) { menu.addAction(mMsgActions->sendAgainAction()); } 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(); } #if 0 menu.addAction(mMsgActions->annotateAction()); menu.addSeparator(); #endif menu.addAction(mMsgActions->addFollowupReminderAction()); if (kmkernel->allowToDebug()) { menu.addSeparator(); menu.addAction(mMsgActions->debugAkonadiSearchAction()); } } 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::setZoomChanged(qreal zoomFactor) { if (mZoomLabelIndicator) { mZoomLabelIndicator->setZoom(zoomFactor); } } 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::slotOpenRecentMessage, 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("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 (kmkernel->allowToDebug()) { QAction *action = new QAction(i18n("&Debug Sieve..."), this); actionCollection()->addAction(QStringLiteral("tools_debug_sieve"), action); connect(action, &QAction::triggered, this, &KMMainWidget::slotDebugSieve); } } { 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("edit-delete-shred"))); 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, &QAction::triggered, this, &KMMainWidget::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)); //----- 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()); //----- "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 { 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(mApplyAllFiltersFolderAction, &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(mApplyAllFiltersFolderRecursiveAction, &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); } QAction *act = actionCollection()->addAction(KStandardAction::Undo, QStringLiteral("kmail_undo")); connect(act, &QAction::triggered, this, &KMMainWidget::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); - mAccountSettings = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Account &Settings"), this); actionCollection()->addAction(QStringLiteral("resource_settings"), mAccountSettings); connect(mAccountSettings, &QAction::triggered, this, &KMMainWidget::slotAccountSettings); } 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. // const 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) const bool single_actions = count == 1; // can we apply loosely single message actions ? (this is false if the VISIBLE selection contains more than one message) const bool singleVisibleMessageSelected = selectedVisibleItems.count() == 1; // can we apply "mass" actions to the selection ? (this is actually always true if the selection is non-empty) const bool mass_actions = count >= 1; // does the selection identify a single thread ? const bool thread_actions = mass_actions && allSelectedBelongToSameThread && mMessagePane->isThreaded(); // can we apply flags to the selected messages ? const 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 && !MailCommon::Util::isVirtualCollection(mCurrentCollection)); 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->newMessageFromTemplateAction()->setEnabled(single_actions && CommonKernel->folderIsTemplates(mCurrentCollection)); filterMenu()->setEnabled(single_actions); mMsgActions->redirectAction()->setEnabled(/*single_actions &&*/ mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection)); if (auto *menuCustom = mMsgActions->customTemplatesMenu()) { menuCustom->forwardActionMenu()->setEnabled(mass_actions); menuCustom->replyActionMenu()->setEnabled(single_actions); menuCustom->replyAllActionMenu()->setEnabled(single_actions); } // "Print" will act on the current message: it will ignore any hidden selection mMsgActions->printAction()->setEnabled(singleVisibleMessageSelected && mMsgView); // "Print preview" will act on the current message: it will ignore any hidden selection if (QAction *printPreviewAction = mMsgActions->printPreviewAction()) { printPreviewAction->setEnabled(singleVisibleMessageSelected && mMsgView); } // "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 << mMsgActions->sendAgainAction(); } if (single_actions) { actionList << mSaveAttachmentsAction; } if (mCurrentCollection.isValid() && FolderArchive::FolderArchiveUtil::resourceSupportArchiving(mCurrentCollection.resource())) { actionList << mArchiveAction; } mGUIClient->unplugActionList(QStringLiteral("messagelist_actionlist")); mGUIClient->plugActionList(QStringLiteral("messagelist_actionlist"), actionList); mMsgActions->sendAgainAction()->setEnabled(statusSendAgain); mSaveAsAction->setEnabled(single_actions); if (currentFolderSettingsIsValid) { updateMoveAction(mCurrentFolderSettings->statistics()); } else { updateMoveAction(false); } const auto col = CommonKernel->collectionFromId(CommonKernel->outboxCollectionFolder().id()); const bool nbMsgOutboxCollectionIsNotNull = (col.statistics().count() > 0); mSendQueued->setEnabled(nbMsgOutboxCollectionIsNotNull); mSendActionMenu->setEnabled(nbMsgOutboxCollectionIsNotNull); 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() && !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); mCopyDecryptedActionMenu->menu()->clear(); 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 (QAction *act = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections)) { act->setEnabled(mCurrentFolderSettings && (mCurrentCollection.rights() & Collection::CanDeleteCollection) && !mCurrentFolderSettings->isSystemFolder() && folderWithContent); } if (QAction *act = mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)) { act->setEnabled(folderWithContent && (mCurrentFolderSettings->count() > 0) && mCurrentFolderSettings->canDeleteMessages()); act->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); const bool isInTrashFolder = (mCurrentFolderSettings && CommonKernel->folderIsTrash(mCurrentCollection)); QAction *moveToTrash = akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash); KMail::Util::setActionTrashOrDelete(moveToTrash, isInTrashFolder); mTrashThreadAction->setIcon(isInTrashFolder ? QIcon::fromTheme(QStringLiteral("edit-delete")) : QIcon::fromTheme(QStringLiteral("edit-delete-shred"))); 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; } } if (mCurrentCollection.parentCollection() != Akonadi::Collection::root()) { mGUIClient->unplugActionList(QStringLiteral("resource_settings")); } else { mGUIClient->plugActionList(QStringLiteral("resource_settings"), {mAccountSettings}); } 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(); const 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::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 (!showSearchDialog()) { 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; } QWidget *KMMainWidget::zoomLabelIndicator() const { return mZoomLabelIndicator; } 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->setHtmlLoadExtDefault(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::slotAccountSettings() { if (!mCurrentCollection.isValid() || mCurrentCollection.parentCollection() != Akonadi::Collection::root()) { return; } auto instance = Akonadi::AgentManager::self()->instance(mCurrentCollection.resource()); if (!instance.isValid()) { return; } instance.configure(this); } 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::slotOpenRecentMessage(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, 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(); KMPrintCommandInfo commandInfo; commandInfo.mMsg = currentItem; commandInfo.mHeaderStylePlugin = messageView()->viewer()->headerStylePlugin(); commandInfo.mFormat = messageView()->viewer()->displayFormatMessageOverwrite(); - commandInfo.mHtmlLoadExtOverride = messageView()->viewer()->htmlLoadExternal(); + commandInfo.mHtmlLoadExtOverride = messageView()->viewer()->htmlLoadExternal(); commandInfo.mPrintPreview = preview; commandInfo.mUseFixedFont = useFixedFont; commandInfo.mOverrideFont = overrideEncoding; commandInfo.mShowSignatureDetails = messageView()->viewer()->showSignatureDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails(); commandInfo.mShowEncryptionDetails = messageView()->viewer()->showEncryptionDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails(); KMPrintCommand *command = new KMPrintCommand(this, commandInfo); 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(); } } void KMMainWidget::slotSetFocusToViewer() { if (messageView() && messageView()->viewer()) { messageView()->viewer()->setFocus(); } } void KMMainWidget::setShowStatusBarMessage(const QString &msg) { if (mCurrentStatusBar) { mCurrentStatusBar->showMessage(msg); } } void KMMainWidget::setupUnifiedMailboxChecker() { if (!KMailSettings::self()->askEnableUnifiedMailboxes()) { return; } const auto ask = [this]() { - if (!KMailSettings::self()->askEnableUnifiedMailboxes()) { - return; - } - - if (kmkernel->accounts().count() <= 1) { - return; - } - - const auto service = Akonadi::ServerManager::self()->agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_unifiedmailbox_agent")); - QDBusInterface iface(service, QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.UnifiedMailboxAgent"), - QDBusConnection::sessionBus(), this); - if (!iface.isValid()) { - return; - } - - QDBusReply reply = iface.call(QStringLiteral("enabledAgent")); - if (!reply.isValid() || bool(reply)) { - return; - } - - const auto answer = KMessageBox::questionYesNo( - this, i18n("You have more than one email account set up. Do you want to enable the Unified Mailbox feature to " - "show unified content of your inbox, sent and drafts folders?\n" - "You can configure unified mailboxes, create custom ones or disable the feature completely in KMail's Plugin settings."), - i18n("Enable Unified Mailboxes?"), - KGuiItem(i18n("Enable Unified Mailboxes"), QStringLiteral("dialog-ok")), - KGuiItem(i18n("Cancel"), QStringLiteral("dialog-cancel"))); - if (answer == KMessageBox::Yes) { - iface.call(QStringLiteral("setEnableAgent"), true); - } - - KMailSettings::self()->setAskEnableUnifiedMailboxes(false); - }; + if (!KMailSettings::self()->askEnableUnifiedMailboxes()) { + return; + } + + if (kmkernel->accounts().count() <= 1) { + return; + } + + const auto service = Akonadi::ServerManager::self()->agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_unifiedmailbox_agent")); + QDBusInterface iface(service, QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.UnifiedMailboxAgent"), + QDBusConnection::sessionBus(), this); + if (!iface.isValid()) { + return; + } + + QDBusReply reply = iface.call(QStringLiteral("enabledAgent")); + if (!reply.isValid() || bool(reply)) { + return; + } + + const auto answer = KMessageBox::questionYesNo( + this, i18n("You have more than one email account set up. Do you want to enable the Unified Mailbox feature to " + "show unified content of your inbox, sent and drafts folders?\n" + "You can configure unified mailboxes, create custom ones or disable the feature completely in KMail's Plugin settings."), + i18n("Enable Unified Mailboxes?"), + KGuiItem(i18n("Enable Unified Mailboxes"), QStringLiteral("dialog-ok")), + KGuiItem(i18n("Cancel"), QStringLiteral("dialog-cancel"))); + if (answer == KMessageBox::Yes) { + iface.call(QStringLiteral("setEnableAgent"), true); + } + + KMailSettings::self()->setAskEnableUnifiedMailboxes(false); + }; connect(kmkernel, &KMKernel::incomingAccountsChanged, this, ask); // Wait for a bit before asking so we at least have the window on screen QTimer::singleShot(500, this, ask); } - diff --git a/src/kmmainwidget.h b/src/kmmainwidget.h index 4379e33d9..d7aed7151 100644 --- a/src/kmmainwidget.h +++ b/src/kmmainwidget.h @@ -1,648 +1,647 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders 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 */ #ifndef KMAIL_KMMAINWIDGET #define KMAIL_KMMAINWIDGET #include "kmail_export.h" #include "kmreaderwin.h" //for inline actions #include "kmkernel.h" // for access to config #include "mailcommon/foldertreewidget.h" #include #include "messageactions.h" #include #include #include #include #include #include #include namespace MailTransport { class Transport; } namespace Akonadi { class Tag; } namespace KMime { class Message; } class QUrl; class QVBoxLayout; class QSplitter; class KMLaunchExternalComponent; class DisplayMessageFormatActionMenu; class QAction; class KActionMenu; class KToggleAction; class KMMetaFilterActionCommand; class CollectionPane; class KMCommand; class KMMoveCommand; class KMTrashMsgCommand; class KRecentFilesAction; class ManageShowCollectionProperties; class KActionMenuTransport; class KActionMenuAccount; class ZoomLabelWidget; namespace KIO { class Job; } namespace KMail { class SearchWindow; class VacationScriptIndicatorWidget; class TagActionManager; class FolderShortcutActionManager; } namespace KSieveUi { class SieveImapPasswordProvider; class ManageSieveScriptsDialog; class VacationManager; } namespace MailCommon { class FolderSelectionDialog; class FavoriteCollectionWidget; class MailFilter; } class QStatusBar; class KMAIL_EXPORT KMMainWidget : public QWidget { Q_OBJECT public: typedef QList PtrList; KMMainWidget(QWidget *parent, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, KSharedConfig::Ptr config = KMKernel::self()->config()); ~KMMainWidget() override; void destruct(); /** Read configuration options before widgets are created. */ void readPreConfig(); /** Read configuration for current folder. */ void readFolderConfig(); /** Write configuration for current folder. */ void writeFolderConfig(); /** Read configuration options after widgets are created. */ void readConfig(); /** Write configuration options. */ void writeConfig(bool force = true); void writeReaderConfig(); /** Easy access to main components of the window. */ KMReaderWin *messageView() const; /** Access to the header list pane. */ CollectionPane *messageListPane() const; Akonadi::Collection currentCollection() const; QSharedPointer currentFolder() const; static void cleanup(); QAction *action(const QString &name); QAction *sendQueuedAction() const; KActionMenuTransport *sendQueueViaMenu() const; /** Returns a list of all KMMainWidgets. Warning, the list itself can be 0. @return the list of all main widgets, or 0 if it is not yet initialized */ static const PtrList *mainWidgetList(); QWidget *vacationScriptIndicator() const; MailCommon::FolderTreeView *folderTreeView() const; /** Returns the XML GUI client. */ KXMLGUIClient *guiClient() const; KMail::TagActionManager *tagActionManager() const; KMail::FolderShortcutActionManager *folderShortcutActionManager() const; void savePaneSelection(); void updatePaneTagComboBox(); void clearViewer(); void addRecentFile(const QUrl &mUrl); void updateQuickSearchLineText(); void populateMessageListStatusFilterCombo(); void initializePluginActions(); Akonadi::Item::List currentSelection() const; QString fullCollectionPath() const; void initializeFilterActions(); /** Clear and create actions for marked filters */ void clearFilterActions(); /** * Convenience function to get the action collection in a list. * * @return a list of action collections. The list only has one item, and * that is the action collection of this main widget as returned * by actionCollection(). */ QList actionCollections() const; void refreshMessageListSelection(); Akonadi::StandardMailActionManager *standardMailActionManager() const; QAction *akonadiStandardAction(Akonadi::StandardActionManager::Type type); QAction *akonadiStandardAction(Akonadi::StandardMailActionManager::Type type); QWidget *zoomLabelIndicator() const; public Q_SLOTS: /** Open a separate viewer window containing the specified message. */ void slotMessageActivated(const Akonadi::Item &); /** Opens mail in the internal viewer. */ void slotMessageSelected(const Akonadi::Item &); void slotItemsFetchedForActivation(KMCommand *command); void slotMessageStatusChangeRequest(const Akonadi::Item &, const Akonadi::MessageStatus &, const Akonadi::MessageStatus &); /** Adds if not existing/removes if existing the tag identified by @p aLabel in all selected messages */ void slotUpdateMessageTagList(const Akonadi::Tag &tag); void slotSelectCollectionFolder(const Akonadi::Collection &col); void slotUpdateConfig(); Q_SIGNALS: void messagesTransfered(bool); void captionChangeRequest(const QString &caption); void recreateGui(); protected: void showEvent(QShowEvent *event) override; private: KMail::MessageActions *messageActions() const; KActionMenu *filterMenu() const; KActionMenu *mailingListActionMenu() const; // Moving messages around /** * This will ask for a destination folder and move the currently selected * messages (in MessageListView) into it. */ void slotMoveSelectedMessageToFolder(); // Copying messages around /** * This will ask for a destination folder and copy the currently selected * messages (in MessageListView) into it. */ void slotCopySelectedMessagesToFolder(); /** * Implements the "move to trash" action */ void slotTrashSelectedMessages(); void slotCheckMail(); void slotCheckMailOnStartup(); /** Trigger the dialog for editing out-of-office scripts. */ void slotEditVacation(const QString &serverName); void slotStartCheckMail(); void slotEndCheckMail(); void restoreCollectionFolderViewConfig(); /** Update message actions */ void updateMessageActions(bool fast = false); void updateMessageActionsDelayed(); /** Update message menu */ void updateMessageMenu(); void slotRemoveDuplicates(); /** Select the given folder If the folder is 0 the intro is shown */ void folderSelected(const Akonadi::Collection &col); /** Start a timer to update message actions */ void startUpdateMessageActionsTimer(); void slotSelectMoreMessageTagList(); void setupActions(); void createWidgets(); void deleteWidgets(); void layoutSplitters(); void newFromTemplate(const Akonadi::Item &); void moveSelectedMessagesToFolder(const Akonadi::Collection &dest); void copySelectedMessagesToFolder(const Akonadi::Collection &dest); KActionCollection *actionCollection() const; /** @return the correct config dialog depending on whether the parent of the mainWidget is a KPart or a KMMainWindow. When dealing with geometries, use this pointer */ KSharedConfig::Ptr config(); void checkAkonadiServerManagerState(); void updateHtmlMenuEntry(); void updateMoveAction(const Akonadi::CollectionStatistics &statistic); void updateMoveAction(bool hasUnreadMails); void updateAllToTrashAction(qint64 statistics); /** Get override character encoding. */ QString overrideEncoding() const; void moveMessageSelected(MessageList::Core::MessageItemSetReference ref, const Akonadi::Collection &dest, bool confirmOnDeletion = true); void copyMessageSelected(const Akonadi::Item::List &selectMsg, const Akonadi::Collection &dest); /** * Move the messages referenced by the specified set to trash. * The set parameter must not be null and the ownership is passed * to this function. */ void trashMessageSelected(MessageList::Core::MessageItemSetReference ref); /** * Set the status of the messages referenced by the specified set, eventually toggling it. * The set parameter must not be null and the ownership is passed to this function. */ void setMessageSetStatus(const Akonadi::Item::List &select, const Akonadi::MessageStatus &status, bool toggle); /** * Toggles a tag for the messages referenced by the specified set. * The set parameter must not be null and the ownership is passed to this function. */ void toggleMessageSetTag(const Akonadi::Item::List &select, const Akonadi::Tag &tag); /** * This applies setMessageSetStatus() on the current thread. */ void setCurrentThreadStatus(const Akonadi::MessageStatus &status, bool toggle); void applyFilters(const Akonadi::Item::List &selectedMessages); void applyFilters(const Akonadi::Collection::List &selectedCols); void applyFilter(const Akonadi::Collection::List &selectedCols, const QString &filter); /** * Internal helper that creates the folder selection dialog used for the * move and copy to folder actions on demand. Only folders where items can * be added are listed. */ MailCommon::FolderSelectionDialog *moveOrCopyToDialog(); /** * Internal helper that creates the folder selection dialog used for * jumping to folders, or adding them as favourites. All folders are listed. */ MailCommon::FolderSelectionDialog *selectFromAllFoldersDialog(); /** * Internal helper that applies the current settings so the * favorite folder view. */ void refreshFavoriteFoldersViewProperties(); void openFilterDialog(const QByteArray &field, const QString &value); void showMessagePopup(const Akonadi::Item &msg, const QUrl &aUrl, const QUrl &imageUrl, const QPoint &aPoint, bool contactAlreadyExists, bool uniqueContactFound, const WebEngineViewer::WebHitTestResult &result); void setZoomChanged(qreal zoomFactor); private Q_SLOTS: void updateFileMenu(); void slotFilter(); void slotManageSieveScripts(); void slotCompose(); void slotPostToML(); void slotExpireFolder(); void slotExpireAll(); void slotArchiveFolder(); void slotRemoveFolder(); void slotEmptyFolder(); void slotClearCurrentFolder(); void slotAddFavoriteFolder(); void slotShowSelectedFolderInPane(); void slotOverrideHtmlLoadExt(); void slotUseTemplate(); void slotTrashThread(); void slotDeleteThread(bool confirmDelete); // completely delete thread void slotUndo(); void slotReadOn(); void slotSaveMsg(); void slotOpenMsg(); void slotSaveAttachments(); void slotJumpToFolder(); void slotCheckVacation(); void slotDebugSieve(); void slotApplyFilters(); void slotApplyFiltersOnFolder(bool recursive); void slotApplyFilterOnFolder(bool recursive); void slotExpandThread(); void slotExpandAllThreads(); void slotCollapseThread(); void slotCollapseAllThreads(); void slotSetThreadStatusUnread(); void slotSetThreadStatusRead(); void slotSetThreadStatusImportant(); void slotSetThreadStatusToAct(); void slotSetThreadStatusWatched(); void slotSetThreadStatusIgnored(); void slotSendQueued(); void slotSendQueuedVia(MailTransport::Transport *transport); void slotOnlineStatus(); void slotUpdateOnlineStatus(KMailSettings::EnumNetworkState::type); void slotMessagePopup(const Akonadi::Item &aMsg, const WebEngineViewer::WebHitTestResult &result, const QPoint &aPoint); void slotContactSearchJobForMessagePopupDone(KJob *job); void slotSelectAllMessages(); void slotFocusQuickSearch(); void slotIntro(); void slotShowStartupFolder(); void slotCopyDecryptedTo(QAction *action); /** Message navigation */ void slotSelectNextMessage(); void slotExtendSelectionToNextMessage(); void slotSelectNextUnreadMessage(); void slotSelectPreviousMessage(); void slotExtendSelectionToPreviousMessage(); void slotSelectPreviousUnreadMessage(); void slotFocusOnNextMessage(); void slotFocusOnPrevMessage(); void slotSelectFirstMessage(); void slotSelectLastMessage(); void slotSelectFocusedMessage(); void slotNextUnreadFolder(); void slotPrevUnreadFolder(); /** etc. */ void slotDisplayCurrentMessage(); void slotShowNewFromTemplate(); void slotDelayedShowNewFromTemplate(KJob *); void slotNewFromTemplate(QAction *); /** Update the undo action */ void slotUpdateUndo(); /** Update html and threaded messages preferences in Folder menu. */ void updateFolderMenu(); /** Settings menu */ /** XML-GUI stuff */ void slotEditNotifications(); /** Slot to reply to a message */ void slotCustomReplyToMsg(const QString &tmpl); void slotCustomReplyAllToMsg(const QString &tmpl); void slotForwardInlineMsg(); void slotForwardAttachedMessage(); void slotRedirectMessage(); void slotCustomForwardMsg(const QString &tmpl); void slotSubjectFilter(); void slotFromFilter(); void slotToFilter(); void slotConfigChanged(); /** Show a splash screen for the longer-lasting operation */ void slotShowBusySplash(); /** Show a message screen explaining that we are currently offline, when an online folder is selected. */ void showOfflinePage(); void showResourceOfflinePage(); void updateVacationScriptStatus(bool active, const QString &serverName = QString()); void slotItemAdded(const Akonadi::Item &, const Akonadi::Collection &col); void slotItemRemoved(const Akonadi::Item &); void slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &from, const Akonadi::Collection &to); void slotCollectionStatisticsChanged(Akonadi::Collection::Id, const Akonadi::CollectionStatistics &); void slotAkonadiStandardActionUpdated(); void slotCollectionChanged(const Akonadi::Collection &, const QSet &); void slotCreateNewTab(bool); void slotUpdateActionsAfterMailChecking(); void slotCreateAddressBookContact(); void slotOpenRecentMessage(const QUrl &url); void slotMoveMessageToTrash(); /** * Called when a "move to trash" operation is completed */ void slotTrashMessagesCompleted(KMTrashMsgCommand *command); /** * Called when a "move" operation is completed */ void slotMoveMessagesCompleted(KMMoveCommand *command); /** * Called when a "copy" operation is completed */ void slotCopyMessagesCompleted(KMCommand *command); void slotRequestFullSearchFromQuickSearch(); void slotFolderChanged(const Akonadi::Collection &); void slotCollectionFetched(int collectionId); void itemsReceived(const Akonadi::Item::List &list); void itemsFetchDone(KJob *job); void slotServerSideSubscription(); void slotServerStateChanged(Akonadi::ServerManager::State state); void slotArchiveMails(); void slotChangeDisplayMessageFormat(MessageViewer::Viewer::DisplayFormatMessage format); void slotCollectionRemoved(const Akonadi::Collection &col); void slotCcFilter(); void slotBandwidth(bool b); void slotDeleteMessages(); void slotMarkAllMessageAsReadInCurrentFolderAndSubfolder(); void slotRemoveDuplicateRecursive(); void slotRedirectCurrentMessage(); void slotEditCurrentVacation(); void slotReplyMessageTo(const KMime::Message::Ptr &message, bool replyToAll); void slotAccountSettings(); private: void slotSetFocusToViewer(); void deleteSelectedMessages(bool confirmDelete); // completely delete message bool showSearchDialog(); void clearCurrentFolder(); void setCurrentCollection(const Akonadi::Collection &col); void showMessageActivities(const QString &str); void slotPageIsScrolledToBottom(bool isAtBottom); void printCurrentMessage(bool preview); void replyCurrentMessageCommand(MessageComposer::ReplyStrategy strategy); void setupUnifiedMailboxChecker(); QAction *filterToAction(MailCommon::MailFilter *filter); Akonadi::Collection::List applyFilterOnCollection(bool recursive); void setShowStatusBarMessage(const QString &msg); - // Message actions QAction *mDeleteAction = nullptr; QAction *mTrashThreadAction = nullptr; QAction *mDeleteThreadAction = nullptr; QAction *mSaveAsAction = nullptr; QAction *mApplyAllFiltersAction = nullptr; QAction *mSaveAttachmentsAction = nullptr; QAction *mOpenAction = nullptr; QAction *mMoveMsgToFolderAction = nullptr; QAction *mCollectionProperties = nullptr; QAction *mSendQueued = nullptr; QAction *mArchiveAction = nullptr; QAction *mSelectAllMessages = nullptr; KActionMenuTransport *mSendActionMenu = nullptr; // Filter actions KActionMenu *mFilterMenu = nullptr; QAction *mExpireConfigAction = nullptr; KActionMenu *mApplyFilterFolderActionsMenu = nullptr; KActionMenu *mApplyFilterFolderRecursiveActionsMenu = nullptr; QAction *mApplyAllFiltersFolderAction = nullptr; QAction *mApplyAllFiltersFolderRecursiveAction = nullptr; // Custom template actions menu KActionMenu *mTemplateMenu = nullptr; KActionMenu *mThreadStatusMenu = nullptr; KActionMenu *mApplyFilterActionsMenu = nullptr; QAction *mCopyActionMenu = nullptr; QAction *mMoveActionMenu = nullptr; QAction *mCopyDecryptedActionMenu = nullptr; QAction *mMarkThreadAsReadAction = nullptr; QAction *mMarkThreadAsUnreadAction = nullptr; KToggleAction *mToggleThreadImportantAction = nullptr; KToggleAction *mToggleThreadToActAction = nullptr; KToggleAction *mWatchThreadAction = nullptr; KToggleAction *mIgnoreThreadAction = nullptr; MailCommon::FavoriteCollectionWidget *mFavoriteCollectionsView = nullptr; Akonadi::FavoriteCollectionsModel *mFavoritesModel = nullptr; KMReaderWin *mMsgView = nullptr; QSplitter *mSplitter1 = nullptr; QSplitter *mSplitter2 = nullptr; QSplitter *mFolderViewSplitter = nullptr; Akonadi::Collection mTemplateFolder; bool mLongFolderList = false; bool mStartupDone = false; bool mWasEverShown = false; bool mHtmlGlobalSetting = false; bool mHtmlLoadExtGlobalSetting = false; bool mFolderHtmlLoadExtPreference = false; bool mReaderWindowActive = false; bool mReaderWindowBelow = false; bool mEnableFavoriteFolderView = false; bool mEnableFolderQuickSearch = false; QPointer mSearchWin; QAction *mExpireFolderAction = nullptr; QAction *mFolderMailingListPropertiesAction = nullptr; QAction *mShowFolderShortcutDialogAction = nullptr; QAction *mArchiveFolderAction = nullptr; QAction *mMessageNewList = nullptr; KToggleAction *mPreferHtmlLoadExtAction = nullptr; QTimer *menutimer = nullptr; QTimer *mShowBusySplashTimer = nullptr; KSieveUi::VacationManager *mVacationManager = nullptr; KActionCollection *mActionCollection = nullptr; QAction *mToolbarActionSeparator = nullptr; QVBoxLayout *mTopLayout = nullptr; bool mDestructed = false; QList mFilterMenuActions; QList mFilterFolderMenuActions; QList mFilterFolderMenuRecursiveActions; QList mFilterTBarActions; QList mFilterCommands; KMail::TagActionManager *mTagActionManager = nullptr; KMail::FolderShortcutActionManager *mFolderShortcutActionManager = nullptr; KSharedConfig::Ptr mConfig; KXMLGUIClient *mGUIClient = nullptr; KMail::MessageActions *mMsgActions = nullptr; Akonadi::StandardMailActionManager *mAkonadiStandardActionManager = nullptr; CollectionPane *mMessagePane = nullptr; QSharedPointer mCurrentFolderSettings; MailCommon::FolderTreeWidget *mFolderTreeWidget = nullptr; KMail::VacationScriptIndicatorWidget *mVacationScriptIndicator = nullptr; bool mVacationIndicatorActive = false; bool mGoToFirstUnreadMessageInSelectedFolder = false; MessageList::Core::PreSelectionMode mPreSelectionMode; QTimer mCheckMailTimer; KSieveUi::SieveImapPasswordProvider *mSievePasswordProvider = nullptr; QPointer mMoveOrCopyToDialog; QPointer mSelectFromAllFoldersDialog; QAction *mServerSideSubscription = nullptr; QAction *mAccountSettings = nullptr; KRecentFilesAction *mOpenRecentAction = nullptr; QPointer mManageSieveDialog; QAction *mQuickSearchAction = nullptr; DisplayMessageFormatActionMenu *mDisplayMessageFormatMenu = nullptr; MessageViewer::Viewer::DisplayFormatMessage mFolderDisplayFormatPreference = MessageViewer::Viewer::UseGlobalSetting; QAction *mSearchMessages = nullptr; KMLaunchExternalComponent *mLaunchExternalComponent = nullptr; ManageShowCollectionProperties *mManageShowCollectionProperties = nullptr; QAction *mShowIntroductionAction = nullptr; KToggleAction *mUseLessBandwidth = nullptr; QAction *mMarkAllMessageAsReadAndInAllSubFolder = nullptr; KActionMenuAccount *mAccountActionMenu = nullptr; QAction *mRemoveDuplicateRecursiveAction = nullptr; Akonadi::Collection mCurrentCollection; QStatusBar *mCurrentStatusBar = nullptr; ZoomLabelWidget *mZoomLabelIndicator = nullptr; }; #endif diff --git a/src/kmreadermainwin.cpp b/src/kmreadermainwin.cpp index 65e8aeb28..e5f9680dd 100644 --- a/src/kmreadermainwin.cpp +++ b/src/kmreadermainwin.cpp @@ -1,731 +1,730 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2011-2018 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 "widgets/zoomlabelwidget.h" #include "kmmainwidget.h" #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include "kmcommands.h" #include #include #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 #include #include #include "kpimtextedit/texttospeech.h" #include using namespace MailCommon; KMReaderMainWin::KMReaderMainWin(MessageViewer::Viewer::DisplayFormatMessage format, bool htmlLoadExtDefault, const QString &name) : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#")) { mReaderWin = new KMReaderWin(this, this, actionCollection()); mReaderWin->setDisplayFormatMessageOverwrite(format); mReaderWin->setHtmlLoadExtDefault(htmlLoadExtDefault); 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")); mZoomLabelIndicator = new ZoomLabelWidget(statusBar()); statusBar()->addPermanentWidget(mZoomLabelIndicator); setZoomChanged(mReaderWin->viewer()->webViewZoomFactor()); if (!mReaderWin->message().isValid()) { menuBar()->hide(); toolBar(QStringLiteral("mainToolBar"))->hide(); } connect(kmkernel, &KMKernel::configChanged, this, &KMReaderMainWin::slotConfigChanged); connect(mReaderWin, &KMReaderWin::showStatusBarMessage, this, &KMReaderMainWin::slotShowMessageStatusBar); connect(mReaderWin, &KMReaderWin::zoomChanged, this, &KMReaderMainWin::setZoomChanged); } KMReaderMainWin::~KMReaderMainWin() { KConfigGroup grp(KMKernel::self()->config()->group("Separate Reader Window")); saveMainWindowSettings(grp); } void KMReaderMainWin::setZoomChanged(qreal zoomFactor) { if (mZoomLabelIndicator) { mZoomLabelIndicator->setZoom(zoomFactor); } } 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() ? static_cast(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); } const bool isInTrashFolder = mParentCollection.isValid() ? CommonKernel->folderIsTrash(mParentCollection) : false; QAction *moveToTrash = actionCollection()->action(QStringLiteral("move_to_trash")); KMail::Util::setActionTrashOrDelete(moveToTrash, isInTrashFolder); updateActions(); } 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); updateActions(); - } void KMReaderMainWin::updateActions() { menuBar()->show(); toolBar(QStringLiteral("mainToolBar"))->show(); if (mMsg.isValid()) { mTagActionManager->updateActionStates(1, mMsg); } } void KMReaderMainWin::slotReplyOrForwardFinished() { if (MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForward()) { close(); } } void KMReaderMainWin::slotSelectMoreMessageTagList() { const Akonadi::Item::List selectedMessages = {mMsg}; 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 KMReaderMainWin::slotUpdateMessageTagList(const Akonadi::Tag &tag) { // Create a persistent set from the current thread. const Akonadi::Item::List selectedMessages = {mMsg}; if (selectedMessages.isEmpty()) { return; } toggleMessageSetTag(selectedMessages, tag); } void KMReaderMainWin::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(); } 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("edit-delete-shred")), 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)); mTagActionManager = new KMail::TagActionManager(this, actionCollection(), mMsgActions, this); connect(mTagActionManager, &KMail::TagActionManager::tagActionTriggered, this, &KMReaderMainWin::slotUpdateMessageTagList); connect(mTagActionManager, &KMail::TagActionManager::tagMoreActionClicked, this, &KMReaderMainWin::slotSelectMoreMessageTagList); mTagActionManager->createActions(); if (mReaderWin->message().isValid()) { mTagActionManager->updateActionStates(1, mReaderWin->message()); } //----- 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(); } else if (col.isValid() && CommonKernel->folderIsTemplates(col)) { menu->addAction(mMsgActions->newMessageFromTemplateAction()); } if (col.isValid() && CommonKernel->folderIsSentMailFolder(col)) { menu->addAction(mMsgActions->sendAgainAction()); 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->allowToDebug()) { menu->addSeparator(); menu->addAction(mMsgActions->debugAkonadiSearchAction()); } } 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/kmreadermainwin.h b/src/kmreadermainwin.h index 5f9733da7..1279d3ca1 100644 --- a/src/kmreadermainwin.h +++ b/src/kmreadermainwin.h @@ -1,109 +1,108 @@ // #ifndef KMReaderMainWin_h #define KMReaderMainWin_h #include "kmail_export.h" #include "secondarywindow.h" #include #include #include #include #include #include class KMReaderWin; class QAction; class KJob; class ZoomLabelWidget; namespace KMail { class MessageActions; class TagActionManager; } namespace KMime { class Message; class Content; } Q_DECLARE_METATYPE(QModelIndex) class KMAIL_EXPORT KMReaderMainWin : public KMail::SecondaryWindow { Q_OBJECT public: KMReaderMainWin(MessageViewer::Viewer::DisplayFormatMessage format, bool htmlLoadExtDefault, const QString &name = QString()); explicit KMReaderMainWin(const QString &name = QString()); KMReaderMainWin(KMime::Content *aMsgPart, MessageViewer::Viewer::DisplayFormatMessage format, const QString &encoding, const QString &name = QString()); ~KMReaderMainWin() override; void setUseFixedFont(bool useFixedFont); MessageViewer::Viewer *viewer() const; /** * take ownership of and show @param msg * * The last two parameters, serNumOfOriginalMessage and nodeIdOffset, are needed when @p msg * is derived from another message, e.g. the user views an encapsulated message in this window. * Then, the reader needs to know about that original message, so those to parameters are passed * onto setOriginalMsg() of KMReaderWin. */ void showMessage(const QString &encoding, const Akonadi::Item &msg, const Akonadi::Collection &parentCollection = Akonadi::Collection()); void showMessage(const QString &encoding, const KMime::Message::Ptr &message); void showMessagePopup(const Akonadi::Item &msg, const QUrl &aUrl, const QUrl &imageUrl, const QPoint &aPoint, bool contactAlreadyExists, bool uniqueContactFound, const WebEngineViewer::WebHitTestResult &result); public Q_SLOTS: void slotForwardInlineMsg(); void slotForwardAttachedMessage(); void slotRedirectMessage(); void slotCustomReplyToMsg(const QString &tmpl); void slotCustomReplyAllToMsg(const QString &tmpl); void slotCustomForwardMsg(const QString &tmpl); private: void slotMessagePopup(const Akonadi::Item &aMsg, const WebEngineViewer::WebHitTestResult &result, const QPoint &aPoint); void slotContactSearchJobForMessagePopupDone(KJob *); void slotTrashMessage(); void slotEditToolbars(); void slotConfigChanged(); void slotUpdateToolbars(); /// This closes the window if the setting to close the window after replying or /// forwarding is set. void slotReplyOrForwardFinished(); void slotCopyItem(QAction *); void slotCopyMoveResult(KJob *job); void slotMoveItem(QAction *action); void slotShowMessageStatusBar(const QString &msg); void copyOrMoveItem(const Akonadi::Collection &collection, bool move); Akonadi::Collection parentCollection() const; void initKMReaderMainWin(); void setupAccel(); QAction *copyActionMenu(QMenu *menu); QAction *moveActionMenu(QMenu *menu); void setZoomChanged(qreal zoomFactor); void updateActions(); void slotSelectMoreMessageTagList(); void toggleMessageSetTag(const Akonadi::Item::List &select, const Akonadi::Tag &tag); void slotUpdateMessageTagList(const Akonadi::Tag &tag); - Akonadi::Collection mParentCollection; Akonadi::Item mMsg; // a few actions duplicated from kmmainwidget QAction *mTrashAction = nullptr; QAction *mSaveAtmAction = nullptr; KMail::MessageActions *mMsgActions = nullptr; KMReaderWin *mReaderWin = nullptr; ZoomLabelWidget *mZoomLabelIndicator = nullptr; KMail::TagActionManager *mTagActionManager = nullptr; }; #endif /*KMReaderMainWin_h*/ diff --git a/src/kmreaderwin.cpp b/src/kmreaderwin.cpp index a2dc58d48..9a84d0d86 100644 --- a/src/kmreaderwin.cpp +++ b/src/kmreaderwin.cpp @@ -1,900 +1,898 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 1997 Markus Wuebben Copyright (c) 2009-2018 Laurent Montel 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. */ // define this to copy all html that is written to the readerwindow to // filehtmlwriter.out in the current working directory #include "kmreaderwin.h" #include "settings/kmailsettings.h" #include "kmmainwidget.h" #include "kmreadermainwin.h" #include "mailcommon/mailkernel.h" #include "dialog/addemailtoexistingcontactdialog.h" #include "job/addemailtoexistingcontactjob.h" #include "kmail-version.h" #include #include #include #include #include #include "kmcommands.h" #include "MailCommon/SendMdnHandler" #include #include "messageviewer/headerstrategy.h" #include "messageviewer/markmessagereadhandler.h" #include "messageviewer/messageviewersettings.h" #include using MessageViewer::CSSHelper; #include "util.h" #include #include #include #include #include "messageviewer/viewer.h" using namespace MessageViewer; #include #include #include #include "MessageComposer/Composer" #include "MessageComposer/TextPart" #include "MessageComposer/InfoPart" #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include // X headers... #undef Never #undef Always #include using namespace KMail; using namespace MailCommon; KMReaderWin::KMReaderWin(QWidget *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QWidget(aParent) , mMainWindow(mainWindow) , mActionCollection(actionCollection) { createActions(); QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setContentsMargins(0, 4, 0, 0); mViewer = new Viewer(this, mainWindow, mActionCollection); connect(mViewer, QOverload::of(&Viewer::urlClicked), this, &KMReaderWin::slotUrlClicked); connect(mViewer, &Viewer::requestConfigSync, kmkernel, &KMKernel::slotRequestConfigSync, Qt::QueuedConnection); // happens anyway on shutdown, so we can skip it there with using a queued connection connect(mViewer, &Viewer::makeResourceOnline, kmkernel, &KMKernel::makeResourceOnline); connect(mViewer, &MessageViewer::Viewer::showReader, this, &KMReaderWin::slotShowReader); connect(mViewer, &MessageViewer::Viewer::showMessage, this, &KMReaderWin::slotShowMessage); connect(mViewer, &MessageViewer::Viewer::showStatusBarMessage, this, &KMReaderWin::showStatusBarMessage); connect(mViewer, &MessageViewer::Viewer::printingFinished, this, &KMReaderWin::slotPrintingFinished); connect(mViewer, &MessageViewer::Viewer::zoomChanged, this, &KMReaderWin::zoomChanged); connect(mViewer, QOverload::of(&Viewer::deleteMessage), this, &KMReaderWin::slotDeleteMessage); mViewer->addMessageLoadedHandler(new MessageViewer::MarkMessageReadHandler(this)); mViewer->addMessageLoadedHandler(new MailCommon::SendMdnHandler(kmkernel, this)); vlay->addWidget(mViewer); readConfig(); } void KMReaderWin::createActions() { KActionCollection *ac = mActionCollection; if (!ac) { return; } // // Message Menu // // new message to mMailToComposeAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("New Message To..."), this); ac->addAction(QStringLiteral("mail_new"), mMailToComposeAction); ac->setShortcutsConfigurable(mMailToComposeAction, false); connect(mMailToComposeAction, &QAction::triggered, this, &KMReaderWin::slotMailtoCompose); // reply to mMailToReplyAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("Reply To..."), this); ac->addAction(QStringLiteral("mailto_reply"), mMailToReplyAction); ac->setShortcutsConfigurable(mMailToReplyAction, false); connect(mMailToReplyAction, &QAction::triggered, this, &KMReaderWin::slotMailtoReply); // forward to mMailToForwardAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18n("Forward To..."), this); ac->setShortcutsConfigurable(mMailToForwardAction, false); ac->addAction(QStringLiteral("mailto_forward"), mMailToForwardAction); connect(mMailToForwardAction, &QAction::triggered, this, &KMReaderWin::slotMailtoForward); // add to addressbook mAddAddrBookAction = new QAction(QIcon::fromTheme(QStringLiteral("contact-new")), i18n("Add to Address Book"), this); ac->setShortcutsConfigurable(mAddAddrBookAction, false); ac->addAction(QStringLiteral("add_addr_book"), mAddAddrBookAction); connect(mAddAddrBookAction, &QAction::triggered, this, &KMReaderWin::slotMailtoAddAddrBook); mAddEmailToExistingContactAction = new QAction(QIcon::fromTheme(QStringLiteral("contact-new")), i18n("Add to Existing Contact"), this); ac->setShortcutsConfigurable(mAddEmailToExistingContactAction, false); ac->addAction(QStringLiteral("add_to_existing_contact"), mAddAddrBookAction); connect(mAddEmailToExistingContactAction, &QAction::triggered, this, &KMReaderWin::slotMailToAddToExistingContact); // open in addressbook mOpenAddrBookAction = new QAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("Open in Address Book"), this); ac->setShortcutsConfigurable(mOpenAddrBookAction, false); ac->addAction(QStringLiteral("openin_addr_book"), mOpenAddrBookAction); connect(mOpenAddrBookAction, &QAction::triggered, this, &KMReaderWin::slotMailtoOpenAddrBook); // bookmark message mAddBookmarksAction = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Bookmark This Link"), this); ac->setShortcutsConfigurable(mAddBookmarksAction, false); ac->addAction(QStringLiteral("add_bookmarks"), mAddBookmarksAction); connect(mAddBookmarksAction, &QAction::triggered, this, &KMReaderWin::slotAddBookmarks); mEditContactAction = new QAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("Edit contact..."), this); ac->setShortcutsConfigurable(mEditContactAction, false); ac->addAction(QStringLiteral("edit_contact"), mOpenAddrBookAction); connect(mEditContactAction, &QAction::triggered, this, &KMReaderWin::slotEditContact); // save URL as mUrlSaveAsAction = new QAction(i18n("Save Link As..."), this); ac->addAction(QStringLiteral("saveas_url"), mUrlSaveAsAction); ac->setShortcutsConfigurable(mUrlSaveAsAction, false); connect(mUrlSaveAsAction, &QAction::triggered, this, &KMReaderWin::slotUrlSave); // find text QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find in Message..."), this); ac->addAction(QStringLiteral("find_in_messages"), action); connect(action, &QAction::triggered, this, &KMReaderWin::slotFind); ac->setDefaultShortcut(action, KStandardShortcut::find().first()); // save Image On Disk mImageUrlSaveAsAction = new QAction(i18n("Save Image On Disk..."), this); ac->addAction(QStringLiteral("saveas_imageurl"), mImageUrlSaveAsAction); ac->setShortcutsConfigurable(mImageUrlSaveAsAction, false); connect(mImageUrlSaveAsAction, &QAction::triggered, this, &KMReaderWin::slotSaveImageOnDisk); // View html options mViewHtmlOptions = new QMenu(i18n("Show HTML Format")); mViewAsHtml = new QAction(i18n("Show HTML format when mail comes from this contact"), mViewHtmlOptions); ac->setShortcutsConfigurable(mViewAsHtml, false); connect(mViewAsHtml, &QAction::triggered, this, &KMReaderWin::slotContactHtmlOptions); mViewAsHtml->setCheckable(true); mViewHtmlOptions->addAction(mViewAsHtml); mLoadExternalReference = new QAction(i18n("Load external reference when mail comes for this contact"), mViewHtmlOptions); ac->setShortcutsConfigurable(mLoadExternalReference, false); connect(mLoadExternalReference, &QAction::triggered, this, &KMReaderWin::slotContactHtmlOptions); mLoadExternalReference->setCheckable(true); mViewHtmlOptions->addAction(mLoadExternalReference); mShareImage = new QAction(i18n("Share image..."), this); ac->addAction(QStringLiteral("share_imageurl"), mShareImage); ac->setShortcutsConfigurable(mShareImage, false); connect(mShareImage, &QAction::triggered, this, &KMReaderWin::slotShareImage); } void KMReaderWin::setUseFixedFont(bool useFixedFont) { mViewer->setUseFixedFont(useFixedFont); } Viewer *KMReaderWin::viewer() const { return mViewer; } bool KMReaderWin::isFixedFont() const { return mViewer->isFixedFont(); } KMReaderWin::~KMReaderWin() { } void KMReaderWin::readConfig(void) { mViewer->readConfig(); } void KMReaderWin::setAttachmentStrategy(const MessageViewer::AttachmentStrategy *strategy) { mViewer->setAttachmentStrategy(strategy); } void KMReaderWin::setOverrideEncoding(const QString &encoding) { mViewer->setOverrideEncoding(encoding); } void KMReaderWin::clearCache() { clear(); } // enter items for the "Important changes" list here: static const char *const kmailChanges[] = { I18N_NOOP("KMail is now based on the Akonadi Personal Information Management framework, which brings many " "changes all around.") }; static const int numKMailChanges = sizeof kmailChanges / sizeof *kmailChanges; // enter items for the "new features" list here, so the main body of // the welcome page can be left untouched (probably much easier for // the translators). Note that the
  • ...
  • tags are added // automatically below: static const char *const kmailNewFeatures[] = { I18N_NOOP("Push email (IMAP IDLE)"), I18N_NOOP("Improved searches"), I18N_NOOP("Support for adding notes (annotations) to mails"), I18N_NOOP("Less GUI freezes, mail checks happen in the background"), I18N_NOOP("Plugins support"), I18N_NOOP("New HTML renderer (QtWebEngine)"), I18N_NOOP("Added Check for Phishing URL"), }; static const int numKMailNewFeatures = sizeof kmailNewFeatures / sizeof *kmailNewFeatures; //static QString KMReaderWin::newFeaturesMD5() { QByteArray str; for (int i = 0; i < numKMailChanges; ++i) { str += kmailChanges[i]; } for (int i = 0; i < numKMailNewFeatures; ++i) { str += kmailNewFeatures[i]; } QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(str); return QLatin1String(md5.result().toBase64()); } void KMReaderWin::displaySplashPage(const QString &templateName, const QVariantHash &_data) { QVariantHash data = _data; if (!data.contains(QLatin1String("icon"))) { data[QStringLiteral("icon")] = QStringLiteral("kmail"); } if (!data.contains(QLatin1String("name"))) { data[QStringLiteral("name")] = i18n("KMail"); } if (!data.contains(QLatin1String("subtitle"))) { data[QStringLiteral("subtitle")] = i18n("The KDE Mail Client"); } mViewer->displaySplashPage(templateName, data, QByteArrayLiteral("kmail")); } void KMReaderWin::displayBusyPage() { displaySplashPage(QStringLiteral("status.html"), { { QStringLiteral("title"), i18n("Retrieving Folder Contents") }, { QStringLiteral("subtext"), i18n("Please wait . . .") } }); } void KMReaderWin::displayOfflinePage() { displaySplashPage(QStringLiteral("status.html"), { { QStringLiteral("title"), i18n("Offline") }, { QStringLiteral("subtext"), i18n("KMail is currently in offline mode. " "Click here to go online . . .

    ") } }); } void KMReaderWin::displayResourceOfflinePage() { displaySplashPage(QStringLiteral("status.html"), { { QStringLiteral("title"), i18n("Offline") }, { QStringLiteral("subtext"), i18n("Account is currently in offline mode. " "Click here to go online . . .

    ") } }); } void KMReaderWin::displayAboutPage() { QVariantHash data; data[QStringLiteral("version")] = QStringLiteral(KDEPIM_VERSION); data[QStringLiteral("firstStart")] = kmkernel->firstStart(); QVariantList features; features.reserve(numKMailNewFeatures); for (int i = 0; i < numKMailNewFeatures; ++i) { features.push_back(i18n(kmailNewFeatures[i])); } data[QStringLiteral("newFeatures")] = features; QVariantList changes; changes.reserve(numKMailChanges); for (int i = 0; i < numKMailChanges; ++i) { features.push_back(i18n(kmailChanges[i])); } data[QStringLiteral("importantChanges")] = changes; displaySplashPage(QStringLiteral(":/about/introduction_kmail.html"), data); } void KMReaderWin::slotFind() { mViewer->slotFind(); } void KMReaderWin::slotCopySelectedText() { QString selection = mViewer->selectedText(); selection.replace(QChar::Nbsp, QLatin1Char(' ')); QApplication::clipboard()->setText(selection); } void KMReaderWin::setMsgPart(KMime::Content *aMsgPart) { mViewer->setMessagePart(aMsgPart); } QString KMReaderWin::copyText() const { return mViewer->selectedText(); } MessageViewer::Viewer::DisplayFormatMessage KMReaderWin::displayFormatMessageOverwrite() const { return mViewer->displayFormatMessageOverwrite(); } void KMReaderWin::setPrintElementBackground(bool printElementBackground) { mViewer->setPrintElementBackground(printElementBackground); } void KMReaderWin::setDisplayFormatMessageOverwrite(MessageViewer::Viewer::DisplayFormatMessage format) { mViewer->setDisplayFormatMessageOverwrite(format); } void KMReaderWin::setHtmlLoadExtDefault(bool loadExtDefault) { mViewer->setHtmlLoadExtDefault(loadExtDefault); } void KMReaderWin::setHtmlLoadExtOverride(bool loadExtOverride) { mViewer->setHtmlLoadExtOverride(loadExtOverride); } bool KMReaderWin::htmlMail() const { return mViewer->htmlMail(); } bool KMReaderWin::htmlLoadExternal() { return mViewer->htmlLoadExternal(); } Akonadi::Item KMReaderWin::message() const { return mViewer->messageItem(); } QWidget *KMReaderWin::mainWindow() const { return mMainWindow; } void KMReaderWin::slotMailtoCompose() { KMCommand *command = new KMMailtoComposeCommand(urlClicked(), message()); command->start(); } void KMReaderWin::slotMailtoForward() { KMCommand *command = new KMMailtoForwardCommand(mMainWindow, urlClicked(), message()); command->start(); } void KMReaderWin::slotMailtoAddAddrBook() { const QUrl url = urlClicked(); if (url.isEmpty()) { return; } const QString emailString = KEmailAddress::decodeMailtoUrl(url); KPIM::AddEmailAddressJob *job = new KPIM::AddEmailAddressJob(emailString, mMainWindow, this); job->start(); } void KMReaderWin::slotMailToAddToExistingContact() { const QUrl url = urlClicked(); if (url.isEmpty()) { return; } const QString emailString = KEmailAddress::decodeMailtoUrl(url); QPointer dlg = new AddEmailToExistingContactDialog(this); if (dlg->exec()) { Akonadi::Item item = dlg->selectedContact(); if (item.isValid()) { AddEmailToExistingContactJob *job = new AddEmailToExistingContactJob(item, emailString, this); job->start(); } } delete dlg; } void KMReaderWin::slotMailtoOpenAddrBook() { const QUrl url = urlClicked(); if (url.isEmpty()) { return; } const QString emailString = KEmailAddress::decodeMailtoUrl(url).toLower(); KPIM::OpenEmailAddressJob *job = new KPIM::OpenEmailAddressJob(emailString, mMainWindow, this); job->start(); } void KMReaderWin::slotAddBookmarks() { const QUrl url = urlClicked(); if (url.isEmpty()) { return; } KMCommand *command = new KMAddBookmarksCommand(url, this); command->start(); } void KMReaderWin::slotUrlSave() { const QUrl url = urlClicked(); if (url.isEmpty()) { return; } KMCommand *command = new KMUrlSaveCommand(url, mMainWindow); command->start(); } void KMReaderWin::slotSaveImageOnDisk() { const QUrl url = imageUrlClicked(); if (url.isEmpty()) { return; } KMCommand *command = new KMUrlSaveCommand(url, mMainWindow); command->start(); } void KMReaderWin::slotMailtoReply() { KMCommand *command = new KMMailtoReplyCommand(mMainWindow, urlClicked(), message(), copyText()); command->start(); } CSSHelper *KMReaderWin::cssHelper() const { return mViewer->cssHelper(); } bool KMReaderWin::htmlLoadExtOverride() const { return mViewer->htmlLoadExtOverride(); } void KMReaderWin::setDecryptMessageOverwrite(bool overwrite) { mViewer->setDecryptMessageOverwrite(overwrite); } const MessageViewer::AttachmentStrategy *KMReaderWin::attachmentStrategy() const { return mViewer->attachmentStrategy(); } QString KMReaderWin::overrideEncoding() const { return mViewer->overrideEncoding(); } KToggleAction *KMReaderWin::toggleFixFontAction() const { return mViewer->toggleFixFontAction(); } QAction *KMReaderWin::mailToComposeAction() const { return mMailToComposeAction; } QAction *KMReaderWin::mailToReplyAction() const { return mMailToReplyAction; } QAction *KMReaderWin::mailToForwardAction() const { return mMailToForwardAction; } QAction *KMReaderWin::addAddrBookAction() const { return mAddAddrBookAction; } QAction *KMReaderWin::openAddrBookAction() const { return mOpenAddrBookAction; } bool KMReaderWin::mimePartTreeIsEmpty() const { return mViewer->mimePartTreeIsEmpty(); } QAction *KMReaderWin::toggleMimePartTreeAction() const { return mViewer->toggleMimePartTreeAction(); } KActionMenu *KMReaderWin::shareServiceUrlMenu() const { return mViewer->shareServiceUrlMenu(); } QList KMReaderWin::viewerPluginActionList(ViewerPluginInterface::SpecificFeatureTypes features) { return mViewer->viewerPluginActionList(features); } QAction *KMReaderWin::selectAllAction() const { return mViewer->selectAllAction(); } QAction *KMReaderWin::copyURLAction() const { return mViewer->copyURLAction(); } QAction *KMReaderWin::copyImageLocation() const { return mViewer->copyImageLocation(); } QAction *KMReaderWin::copyAction() const { return mViewer->copyAction(); } QAction *KMReaderWin::viewSourceAction() const { return mViewer->viewSourceAction(); } QAction *KMReaderWin::saveAsAction() const { return mViewer->saveAsAction(); } QAction *KMReaderWin::findInMessageAction() const { return mViewer->findInMessageAction(); } QAction *KMReaderWin::urlOpenAction() const { return mViewer->urlOpenAction(); } QAction *KMReaderWin::urlSaveAsAction() const { return mUrlSaveAsAction; } QAction *KMReaderWin::addBookmarksAction() const { return mAddBookmarksAction; } void KMReaderWin::setPrinting(bool enable) { mViewer->setPrinting(enable); } QAction *KMReaderWin::speakTextAction() const { return mViewer->speakTextAction(); } QAction *KMReaderWin::downloadImageToDiskAction() const { return mImageUrlSaveAsAction; } void KMReaderWin::clear(bool force) { mViewer->clear(force ? MimeTreeParser::Force : MimeTreeParser::Delayed); } void KMReaderWin::setMessage(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { qCDebug(KMAIL_LOG) << Q_FUNC_INFO << parentWidget(); mViewer->setMessageItem(item, updateMode); } void KMReaderWin::setMessage(const KMime::Message::Ptr &message) { mViewer->setMessage(message); } QUrl KMReaderWin::urlClicked() const { return mViewer->urlClicked(); } QUrl KMReaderWin::imageUrlClicked() const { return mViewer->imageUrlClicked(); } void KMReaderWin::update(bool force) { mViewer->update(force ? MimeTreeParser::Force : MimeTreeParser::Delayed); } void KMReaderWin::slotUrlClicked(const Akonadi::Item &item, const QUrl &url) { if (item.isValid() && item.parentCollection().isValid()) { const auto col = CommonKernel->collectionFromId(item.parentCollection().id()); QSharedPointer fd = FolderSettings::forCollection(col, false); KMail::Util::handleClickedURL(url, fd, item.parentCollection()); return; } //No folder so we can't have identity and template. KMail::Util::handleClickedURL(url); } void KMReaderWin::slotShowReader(KMime::Content *msgPart, bool html, const QString &encoding) { const MessageViewer::Viewer::DisplayFormatMessage format = html ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text; KMReaderMainWin *win = new KMReaderMainWin(msgPart, format, encoding); win->show(); } void KMReaderWin::slotShowMessage(const KMime::Message::Ptr &message, const QString &encoding) { KMReaderMainWin *win = new KMReaderMainWin(); win->showMessage(encoding, message); win->show(); } void KMReaderWin::slotDeleteMessage(const Akonadi::Item &item) { if (!item.isValid()) { return; } KMTrashMsgCommand *command = new KMTrashMsgCommand(item.parentCollection(), item, -1); command->start(); } bool KMReaderWin::printSelectedText(bool preview) { const QString str = mViewer->selectedText(); if (str.isEmpty()) { return false; } ::MessageComposer::Composer *composer = new ::MessageComposer::Composer; composer->textPart()->setCleanPlainText(str); composer->textPart()->setWrappedPlainText(str); KMime::Message::Ptr messagePtr = message().payload(); composer->infoPart()->setFrom(messagePtr->from()->asUnicodeString()); composer->infoPart()->setTo(QStringList() << messagePtr->to()->asUnicodeString()); composer->infoPart()->setCc(QStringList() << messagePtr->cc()->asUnicodeString()); composer->infoPart()->setSubject(messagePtr->subject()->asUnicodeString()); composer->setProperty("preview", preview); connect(composer, &::MessageComposer::Composer::result, this, &KMReaderWin::slotPrintComposeResult); composer->start(); return true; } void KMReaderWin::slotPrintComposeResult(KJob *job) { const bool preview = job->property("preview").toBool(); ::MessageComposer::Composer *composer = dynamic_cast< ::MessageComposer::Composer * >(job); Q_ASSERT(composer); if (composer->error() == ::MessageComposer::Composer::NoError) { Q_ASSERT(composer->resultMessages().size() == 1); Akonadi::Item printItem; printItem.setPayload(composer->resultMessages().constFirst()); Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().constFirst()), printItem); const bool useFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont(); const QString overrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); - KMPrintCommandInfo commandInfo; commandInfo.mMsg = printItem; commandInfo.mHeaderStylePlugin = mViewer->headerStylePlugin(); commandInfo.mFormat = mViewer->displayFormatMessageOverwrite(); - commandInfo.mHtmlLoadExtOverride = mViewer->htmlLoadExternal(); + commandInfo.mHtmlLoadExtOverride = mViewer->htmlLoadExternal(); commandInfo.mPrintPreview = preview; commandInfo.mUseFixedFont = useFixedFont; commandInfo.mOverrideFont = overrideEncoding; commandInfo.mShowSignatureDetails = mViewer->showSignatureDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails(); commandInfo.mShowEncryptionDetails = mViewer->showEncryptionDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails(); - KMPrintCommand *command = new KMPrintCommand(this, commandInfo); command->start(); } else { if (static_cast(job)->uiDelegate()) { static_cast(job)->uiDelegate()->showErrorMessage(); } else { qCWarning(KMAIL_LOG) << "Composer for printing failed:" << composer->errorString(); } } } void KMReaderWin::clearContactItem() { mSearchedContact = Akonadi::Item(); mSearchedAddress = KContacts::Addressee(); mLoadExternalReference->setChecked(false); mViewAsHtml->setChecked(false); } void KMReaderWin::setContactItem(const Akonadi::Item &contact, const KContacts::Addressee &address) { mSearchedContact = contact; mSearchedAddress = address; updateHtmlActions(); } void KMReaderWin::updateHtmlActions() { if (!mSearchedContact.isValid()) { mLoadExternalReference->setChecked(false); mViewAsHtml->setChecked(false); } else { const QStringList customs = mSearchedAddress.customs(); for (const QString &custom : customs) { if (custom.contains(QLatin1String("MailPreferedFormatting"))) { const QString value = mSearchedAddress.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("MailPreferedFormatting")); mViewAsHtml->setChecked(value == QLatin1String("HTML")); } else if (custom.contains(QLatin1String("MailAllowToRemoteContent"))) { const QString value = mSearchedAddress.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("MailAllowToRemoteContent")); mLoadExternalReference->setChecked((value == QLatin1String("TRUE"))); } } } } void KMReaderWin::slotContactHtmlOptions() { const QUrl url = urlClicked(); if (url.isEmpty()) { return; } const QString emailString = KEmailAddress::decodeMailtoUrl(url).toLower(); KPIM::AddEmailDiplayJob *job = new KPIM::AddEmailDiplayJob(emailString, mMainWindow, this); job->setRemoteContent(mLoadExternalReference->isChecked()); job->setShowAsHTML(mViewAsHtml->isChecked()); job->setContact(mSearchedContact); job->start(); } void KMReaderWin::slotEditContact() { if (mSearchedContact.isValid()) { QPointer dlg = new Akonadi::ContactEditorDialog(Akonadi::ContactEditorDialog::EditMode, this); connect(dlg.data(), &Akonadi::ContactEditorDialog::contactStored, this, &KMReaderWin::contactStored); connect(dlg.data(), &Akonadi::ContactEditorDialog::error, this, &KMReaderWin::slotContactEditorError); dlg->setContact(mSearchedContact); dlg->exec(); delete dlg; } } void KMReaderWin::slotContactEditorError(const QString &error) { KMessageBox::error(this, i18n("Contact cannot be stored: %1", error), i18n("Failed to store contact")); } void KMReaderWin::contactStored(const Akonadi::Item &item) { Q_UNUSED(item); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Contact modified successfully")); } QAction *KMReaderWin::saveMessageDisplayFormatAction() const { return mViewer->saveMessageDisplayFormatAction(); } QAction *KMReaderWin::resetMessageDisplayFormatAction() const { return mViewer->resetMessageDisplayFormatAction(); } QAction *KMReaderWin::editContactAction() const { return mEditContactAction; } QMenu *KMReaderWin::viewHtmlOption() const { return mViewHtmlOptions; } QAction *KMReaderWin::shareImage() const { return mShareImage; } QAction *KMReaderWin::addToExistingContactAction() const { return mAddEmailToExistingContactAction; } void KMReaderWin::slotShareImage() { KMCommand *command = new KMShareImageCommand(imageUrlClicked(), this); command->start(); } QList KMReaderWin::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const { return mViewer->interceptorUrlActions(result); } void KMReaderWin::slotPrintingFinished() { if (mViewer->printingMode()) { deleteLater(); } } diff --git a/src/messageactions.cpp b/src/messageactions.cpp index 0128811cb..878dd6cd9 100644 --- a/src/messageactions.cpp +++ b/src/messageactions.cpp @@ -1,755 +1,754 @@ /* 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 #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); } 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 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); mSendAgainAction = new QAction(i18n("Send A&gain..."), this); ac->addAction(QStringLiteral("send_again"), mSendAgainAction); connect(mSendAgainAction, &QAction::triggered, this, &MessageActions::slotResendMessage); mNewMessageFromTemplateAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New Message From &Template"), this); ac->addAction(QStringLiteral("use_template"), mNewMessageFromTemplateAction); connect(mNewMessageFromTemplateAction, &QAction::triggered, this, &MessageActions::slotUseTemplate); ac->setDefaultShortcut(mNewMessageFromTemplateAction, QKeySequence(Qt::SHIFT + Qt::Key_N)); updateActions(); } MessageActions::~MessageActions() { delete mCustomTemplatesMenu; } TemplateParser::CustomTemplatesMenu *MessageActions::customTemplatesMenu() const { return mCustomTemplatesMenu; } void MessageActions::slotUseTemplate() { if (!mCurrentItem.isValid()) { return; } KMCommand *command = new KMUseTemplateCommand(mParent, mCurrentItem); command->start(); } 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::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); mAddFollowupReminderAction->setEnabled(uniqItem); if (!mCurrentItem.hasAttribute()) { mAnnotateAction->setText(i18n("Add Note...")); } else { mAnnotateAction->setText(i18n("Edit Note...")); } mStatusMenu->setEnabled(multiVisible); mPrintAction->setEnabled(mMessageView != nullptr); mPrintPreviewAction->setEnabled(mMessageView != nullptr); 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); } } } void MessageActions::slotUpdateActionsFetchDone(KJob *job) { if (job->error()) { return; } Akonadi::ItemFetchJob *fetchJob = static_cast(job); if (fetchJob->items().isEmpty()) { return; } const Akonadi::Item messageItem = fetchJob->items().constFirst(); 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(KStringHandler::rsqueeze(i18n("Mailing List Name: %1", listId), 40)); } 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; KMPrintCommandInfo commandInfo; commandInfo.mMsg = message; commandInfo.mHeaderStylePlugin = mMessageView->viewer()->headerStylePlugin(); commandInfo.mFormat = mMessageView->viewer()->displayFormatMessageOverwrite(); - commandInfo.mHtmlLoadExtOverride = mMessageView->viewer()->htmlLoadExternal(); + commandInfo.mHtmlLoadExtOverride = mMessageView->viewer()->htmlLoadExternal(); commandInfo.mPrintPreview = preview; commandInfo.mUseFixedFont = useFixedFont; commandInfo.mOverrideFont = overrideEncoding; commandInfo.mShowSignatureDetails = mMessageView->viewer()->showSignatureDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails(); commandInfo.mShowEncryptionDetails = mMessageView->viewer()->showEncryptionDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails(); - KMPrintCommand *command = new KMPrintCommand(mParent, commandInfo); command->start(); } } else { qCWarning(KMAIL_LOG) << "MessageActions::printMessage impossible to do it if we don't have a viewer"; } } 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(QLatin1String("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::debugAkonadiSearchAction() const { return mDebugAkonadiSearchAction; } QAction *MessageActions::addFollowupReminderAction() const { return mAddFollowupReminderAction; } 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::slotResendMessage() { if (!mCurrentItem.isValid()) { return; } KMCommand *command = new KMResendMessageCommand(mParent, mCurrentItem); command->start(); } QAction *MessageActions::newMessageFromTemplateAction() const { return mNewMessageFromTemplateAction; } QAction *MessageActions::sendAgainAction() const { return mSendAgainAction; } void MessageActions::slotAddFollowupReminder() { if (!mCurrentItem.isValid()) { return; } QPointer dlg = new MessageComposer::FollowUpReminderSelectDateDialog(mParent); 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/search/checkindexingmanager.cpp b/src/search/checkindexingmanager.cpp index 2da38647b..619b9cd32 100644 --- a/src/search/checkindexingmanager.cpp +++ b/src/search/checkindexingmanager.cpp @@ -1,165 +1,165 @@ /* Copyright (C) 2016-2018 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()) { QDBusInterface interfaceAkonadiIndexer(PimCommon::MailUtil::indexerServiceName(), QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.Indexer")); if (interfaceAkonadiIndexer.isValid()) { qCDebug(KMAIL_LOG) << "Reindex collections :" << mCollectionsIndexed; 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(); + 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); } } } diff --git a/src/widgets/zoomlabelwidget.cpp b/src/widgets/zoomlabelwidget.cpp index 682d5b02c..e0f0d5e99 100644 --- a/src/widgets/zoomlabelwidget.cpp +++ b/src/widgets/zoomlabelwidget.cpp @@ -1,42 +1,40 @@ /* Copyright (C) 2018 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "zoomlabelwidget.h" #include ZoomLabelWidget::ZoomLabelWidget(QWidget *parent) : QLabel(parent) { - } ZoomLabelWidget::~ZoomLabelWidget() { - } void ZoomLabelWidget::setZoom(qreal zoomFactor) { if (zoomFactor != 100.0) { setText(i18n("Zoom: %1%", zoomFactor)); show(); } else { hide(); } } diff --git a/src/widgets/zoomlabelwidget.h b/src/widgets/zoomlabelwidget.h index d16deef47..0aa72461c 100644 --- a/src/widgets/zoomlabelwidget.h +++ b/src/widgets/zoomlabelwidget.h @@ -1,36 +1,35 @@ /* Copyright (C) 2018 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - #ifndef ZOOMLABELWIDGET_H #define ZOOMLABELWIDGET_H #include class ZoomLabelWidget : public QLabel { Q_OBJECT public: explicit ZoomLabelWidget(QWidget *parent = nullptr); ~ZoomLabelWidget(); void setZoom(qreal zoomFactor); }; #endif // ZOOMLABELWIDGET_H