diff --git a/agents/archivemailagent/addarchivemaildialog.cpp b/agents/archivemailagent/addarchivemaildialog.cpp index 31b364a85..4b8380e44 100644 --- a/agents/archivemailagent/addarchivemaildialog.cpp +++ b/agents/archivemailagent/addarchivemaildialog.cpp @@ -1,237 +1,237 @@ /* Copyright (C) 2012-2019 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 "addarchivemaildialog.h" #include "widgets/formatcombobox.h" #include "widgets/unitcombobox.h" #include "MailCommon/FolderRequester" #include #include #include #include #include #include #include #include #include #include #include #include AddArchiveMailDialog::AddArchiveMailDialog(ArchiveMailInfo *info, QWidget *parent) : QDialog(parent) , mInfo(info) { if (info) { setWindowTitle(i18n("Modify Archive Mail")); } else { setWindowTitle(i18n("Add Archive Mail")); } setModal(true); setWindowIcon(QIcon::fromTheme(QStringLiteral("kmail"))); QVBoxLayout *topLayout = new QVBoxLayout(this); QGridLayout *mainLayout = new QGridLayout; - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); int row = 0; QLabel *folderLabel = new QLabel(i18n("&Folder:"), this); mainLayout->addWidget(folderLabel, row, 0); mFolderRequester = new MailCommon::FolderRequester(this); mFolderRequester->setObjectName(QStringLiteral("folder_requester")); mFolderRequester->setMustBeReadWrite(false); mFolderRequester->setNotAllowToCreateNewFolder(true); connect(mFolderRequester, &MailCommon::FolderRequester::folderChanged, this, &AddArchiveMailDialog::slotFolderChanged); if (info) { //Don't autorize to modify folder when we just modify item. mFolderRequester->setEnabled(false); } folderLabel->setBuddy(mFolderRequester); mainLayout->addWidget(mFolderRequester, row, 1); ++row; QLabel *formatLabel = new QLabel(i18n("Format:"), this); formatLabel->setObjectName(QStringLiteral("label_format")); mainLayout->addWidget(formatLabel, row, 0); mFormatComboBox = new FormatComboBox(this); mainLayout->addWidget(mFormatComboBox, row, 1); ++row; mRecursiveCheckBox = new QCheckBox(i18n("Archive all subfolders"), this); mRecursiveCheckBox->setObjectName(QStringLiteral("recursive_checkbox")); mainLayout->addWidget(mRecursiveCheckBox, row, 0, 1, 2, Qt::AlignLeft); mRecursiveCheckBox->setChecked(true); ++row; QLabel *pathLabel = new QLabel(i18n("Path:"), this); mainLayout->addWidget(pathLabel, row, 0); pathLabel->setObjectName(QStringLiteral("path_label")); mPath = new KUrlRequester(this); mPath->lineEdit()->setTrapReturnKey(true); connect(mPath, &KUrlRequester::textChanged, this, &AddArchiveMailDialog::slotUpdateOkButton); mPath->setMode(KFile::Directory); mainLayout->addWidget(mPath); ++row; QLabel *dateLabel = new QLabel(i18n("Backup each:"), this); dateLabel->setObjectName(QStringLiteral("date_label")); mainLayout->addWidget(dateLabel, row, 0); QHBoxLayout *hlayout = new QHBoxLayout; mDays = new QSpinBox(this); mDays->setMinimum(1); mDays->setMaximum(3600); hlayout->addWidget(mDays); mUnits = new UnitComboBox(this); hlayout->addWidget(mUnits); mainLayout->addLayout(hlayout, row, 1); ++row; QLabel *maxCountlabel = new QLabel(i18n("Maximum number of archive:"), this); mainLayout->addWidget(maxCountlabel, row, 0); mMaximumArchive = new QSpinBox(this); mMaximumArchive->setMinimum(0); mMaximumArchive->setMaximum(9999); mMaximumArchive->setSpecialValueText(i18n("unlimited")); maxCountlabel->setBuddy(mMaximumArchive); mainLayout->addWidget(mMaximumArchive, row, 1); ++row; mainLayout->addWidget(new KSeparator, row, 0, row, 2); mainLayout->setColumnStretch(1, 1); mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), row, 0); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &AddArchiveMailDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &AddArchiveMailDialog::reject); if (mInfo) { load(mInfo); } else { mOkButton->setEnabled(false); } topLayout->addLayout(mainLayout); topLayout->addWidget(buttonBox); // Make it a bit bigger, else the folder requester cuts off the text too early resize(500, minimumSize().height()); } AddArchiveMailDialog::~AddArchiveMailDialog() { } void AddArchiveMailDialog::load(ArchiveMailInfo *info) { mPath->setUrl(info->url()); mRecursiveCheckBox->setChecked(info->saveSubCollection()); mFolderRequester->setCollection(Akonadi::Collection(info->saveCollectionId())); mFormatComboBox->setFormat(info->archiveType()); mDays->setValue(info->archiveAge()); mUnits->setUnit(info->archiveUnit()); mMaximumArchive->setValue(info->maximumArchiveCount()); slotUpdateOkButton(); } ArchiveMailInfo *AddArchiveMailDialog::info() { if (!mInfo) { mInfo = new ArchiveMailInfo(); } mInfo->setSaveSubCollection(mRecursiveCheckBox->isChecked()); mInfo->setArchiveType(mFormatComboBox->format()); mInfo->setSaveCollectionId(mFolderRequester->collection().id()); mInfo->setUrl(mPath->url()); mInfo->setArchiveAge(mDays->value()); mInfo->setArchiveUnit(mUnits->unit()); mInfo->setMaximumArchiveCount(mMaximumArchive->value()); return mInfo; } void AddArchiveMailDialog::slotUpdateOkButton() { const bool valid = (!mPath->lineEdit()->text().trimmed().isEmpty() && !mPath->url().isEmpty() && mFolderRequester->collection().isValid()); mOkButton->setEnabled(valid); } void AddArchiveMailDialog::slotFolderChanged(const Akonadi::Collection &collection) { Q_UNUSED(collection); slotUpdateOkButton(); } void AddArchiveMailDialog::setArchiveType(MailCommon::BackupJob::ArchiveType type) { mFormatComboBox->setFormat(type); } MailCommon::BackupJob::ArchiveType AddArchiveMailDialog::archiveType() const { return mFormatComboBox->format(); } void AddArchiveMailDialog::setRecursive(bool b) { mRecursiveCheckBox->setChecked(b); } bool AddArchiveMailDialog::recursive() const { return mRecursiveCheckBox->isChecked(); } void AddArchiveMailDialog::setSelectedFolder(const Akonadi::Collection &collection) { mFolderRequester->setCollection(collection); } Akonadi::Collection AddArchiveMailDialog::selectedFolder() const { return mFolderRequester->collection(); } QUrl AddArchiveMailDialog::path() const { return mPath->url(); } void AddArchiveMailDialog::setPath(const QUrl &url) { mPath->setUrl(url); } void AddArchiveMailDialog::setMaximumArchiveCount(int max) { mMaximumArchive->setValue(max); } int AddArchiveMailDialog::maximumArchiveCount() const { return mMaximumArchive->value(); } diff --git a/agents/archivemailagent/archivemailwidget.cpp b/agents/archivemailagent/archivemailwidget.cpp index 03004ff8b..1923a4a22 100644 --- a/agents/archivemailagent/archivemailwidget.cpp +++ b/agents/archivemailagent/archivemailwidget.cpp @@ -1,357 +1,357 @@ /* Copyright (C) 2015-2019 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 "archivemailwidget.h" #include "addarchivemaildialog.h" #include "archivemailagentutil.h" #include "kmail-version.h" #include #include #include #include #include #include #include #include #include namespace { inline QString archiveMailCollectionPattern() { return QStringLiteral("ArchiveMailCollection \\d+"); } static const char myConfigGroupName[] = "ArchiveMailDialog"; } ArchiveMailItem::ArchiveMailItem(QTreeWidget *parent) : QTreeWidgetItem(parent) { } ArchiveMailItem::~ArchiveMailItem() { delete mInfo; } void ArchiveMailItem::setInfo(ArchiveMailInfo *info) { mInfo = info; } ArchiveMailInfo *ArchiveMailItem::info() const { return mInfo; } ArchiveMailWidget::ArchiveMailWidget(const KSharedConfigPtr &config, QWidget *parent, const QVariantList &args) : Akonadi::AgentConfigurationBase(config, parent, args) , mChanged(false) { QWidget *w = new QWidget(parent); mWidget.setupUi(w); parent->layout()->addWidget(w); QStringList headers; headers << i18n("Name") << i18n("Last archive") << i18n("Next archive in") << i18n("Storage directory"); mWidget.treeWidget->setHeaderLabels(headers); mWidget.treeWidget->setObjectName(QStringLiteral("treewidget")); mWidget.treeWidget->setSortingEnabled(true); mWidget.treeWidget->setRootIsDecorated(false); mWidget.treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); mWidget.treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(mWidget.treeWidget, &QWidget::customContextMenuRequested, this, &ArchiveMailWidget::slotCustomContextMenuRequested); connect(mWidget.removeItem, &QAbstractButton::clicked, this, &ArchiveMailWidget::slotRemoveItem); connect(mWidget.modifyItem, &QAbstractButton::clicked, this, &ArchiveMailWidget::slotModifyItem); connect(mWidget.addItem, &QAbstractButton::clicked, this, &ArchiveMailWidget::slotAddItem); connect(mWidget.treeWidget, &QTreeWidget::itemChanged, this, &ArchiveMailWidget::slotItemChanged); connect(mWidget.treeWidget, &QTreeWidget::itemSelectionChanged, this, &ArchiveMailWidget::updateButtons); connect(mWidget.treeWidget, &QTreeWidget::itemDoubleClicked, this, &ArchiveMailWidget::slotModifyItem); updateButtons(); KAboutData aboutData( QStringLiteral("archivemailagent"), i18n("Archive Mail Agent"), QStringLiteral(KDEPIM_VERSION), i18n("Archive emails automatically."), KAboutLicense::GPL_V2, i18n("Copyright (C) 2014-2019 Laurent Montel")); aboutData.addAuthor(i18n("Laurent Montel"), i18n("Maintainer"), QStringLiteral("montel@kde.org")); aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); setKAboutData(aboutData); } ArchiveMailWidget::~ArchiveMailWidget() { } void ArchiveMailWidget::slotCustomContextMenuRequested(const QPoint &) { const QList listItems = mWidget.treeWidget->selectedItems(); QMenu menu(parentWidget()); menu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add..."), this, &ArchiveMailWidget::slotAddItem); if (!listItems.isEmpty()) { if (listItems.count() == 1) { menu.addAction(i18n("Open Containing Folder..."), this, &ArchiveMailWidget::slotOpenFolder); menu.addSeparator(); menu.addAction(i18n("Archive now"), this, &ArchiveMailWidget::slotArchiveNow); } menu.addSeparator(); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete"), this, &ArchiveMailWidget::slotRemoveItem); } menu.exec(QCursor::pos()); } void ArchiveMailWidget::updateButtons() { const QList listItems = mWidget.treeWidget->selectedItems(); if (listItems.isEmpty()) { mWidget.removeItem->setEnabled(false); mWidget.modifyItem->setEnabled(false); } else if (listItems.count() == 1) { mWidget.removeItem->setEnabled(true); mWidget.modifyItem->setEnabled(true); } else { mWidget.removeItem->setEnabled(true); mWidget.modifyItem->setEnabled(false); } } void ArchiveMailWidget::needReloadConfig() { //TODO add messagebox which informs that we save settings here. mWidget.treeWidget->clear(); load(); } void ArchiveMailWidget::load() { const auto group = config()->group(myConfigGroupName); mWidget.treeWidget->header()->restoreState(group.readEntry("HeaderState", QByteArray())); const QStringList collectionList = config()->groupList().filter(QRegularExpression(archiveMailCollectionPattern())); const int numberOfCollection = collectionList.count(); for (int i = 0; i < numberOfCollection; ++i) { KConfigGroup group = config()->group(collectionList.at(i)); ArchiveMailInfo *info = new ArchiveMailInfo(group); if (info->isValid()) { createOrUpdateItem(info); } else { delete info; } } } void ArchiveMailWidget::createOrUpdateItem(ArchiveMailInfo *info, ArchiveMailItem *item) { if (!item) { item = new ArchiveMailItem(mWidget.treeWidget); } item->setText(ArchiveMailWidget::Name, i18n("Folder: %1", MailCommon::Util::fullCollectionPath(Akonadi::Collection(info->saveCollectionId())))); item->setCheckState(ArchiveMailWidget::Name, info->isEnabled() ? Qt::Checked : Qt::Unchecked); item->setText(ArchiveMailWidget::StorageDirectory, info->url().toLocalFile()); if (info->lastDateSaved().isValid()) { item->setText(ArchiveMailWidget::LastArchiveDate, QLocale().toString(info->lastDateSaved(), QLocale::ShortFormat)); updateDiffDate(item, info); } else { - item->setBackgroundColor(ArchiveMailWidget::NextArchive, Qt::green); + item->setBackground(ArchiveMailWidget::NextArchive, Qt::green); } item->setInfo(info); } void ArchiveMailWidget::updateDiffDate(ArchiveMailItem *item, ArchiveMailInfo *info) { const QDate diffDate = ArchiveMailAgentUtil::diffDate(info); const qint64 diff = QDate::currentDate().daysTo(diffDate); item->setText(ArchiveMailWidget::NextArchive, i18np("Tomorrow", "%1 days", diff)); if (diff < 0) { if (info->isEnabled()) { - item->setBackgroundColor(ArchiveMailWidget::NextArchive, Qt::red); + item->setBackground(ArchiveMailWidget::NextArchive, Qt::red); } else { - item->setBackgroundColor(ArchiveMailWidget::NextArchive, Qt::lightGray); + item->setBackground(ArchiveMailWidget::NextArchive, Qt::lightGray); } } else { item->setToolTip(ArchiveMailWidget::NextArchive, i18n("Archive will be done %1", QLocale().toString(diffDate, QLocale::ShortFormat))); } } bool ArchiveMailWidget::save() const { if (!mChanged) { return false; } // first, delete all filter groups: const QStringList filterGroups = config()->groupList().filter(QRegularExpression(archiveMailCollectionPattern())); for (const QString &group : filterGroups) { config()->deleteGroup(group); } const int numberOfItem(mWidget.treeWidget->topLevelItemCount()); for (int i = 0; i < numberOfItem; ++i) { ArchiveMailItem *mailItem = static_cast(mWidget.treeWidget->topLevelItem(i)); if (mailItem->info()) { KConfigGroup group = config()->group(ArchiveMailAgentUtil::archivePattern.arg(mailItem->info()->saveCollectionId())); mailItem->info()->writeConfig(group); } } auto group = config()->group(myConfigGroupName); group.writeEntry("HeaderState", mWidget.treeWidget->header()->saveState()); return true; } void ArchiveMailWidget::slotRemoveItem() { const QList listItems = mWidget.treeWidget->selectedItems(); if (KMessageBox::warningYesNo(parentWidget(), i18n("Do you want to delete the selected items?"), i18n("Remove items")) == KMessageBox::No) { return; } for (QTreeWidgetItem *item : listItems) { delete item; } mChanged = true; updateButtons(); } void ArchiveMailWidget::slotModifyItem() { const QList listItems = mWidget.treeWidget->selectedItems(); if (listItems.count() == 1) { QTreeWidgetItem *item = listItems.at(0); if (!item) { return; } ArchiveMailItem *archiveItem = static_cast(item); QPointer dialog = new AddArchiveMailDialog(archiveItem->info(), parentWidget()); if (dialog->exec()) { ArchiveMailInfo *info = dialog->info(); createOrUpdateItem(info, archiveItem); mChanged = true; } delete dialog; } } void ArchiveMailWidget::slotAddItem() { QPointer dialog = new AddArchiveMailDialog(nullptr, parentWidget()); if (dialog->exec()) { ArchiveMailInfo *info = dialog->info(); if (verifyExistingArchive(info)) { KMessageBox::error(parentWidget(), i18n("Cannot add a second archive for this folder. Modify the existing one instead."), i18n("Add Archive Mail")); delete info; } else { createOrUpdateItem(info); updateButtons(); mChanged = true; } } delete dialog; } bool ArchiveMailWidget::verifyExistingArchive(ArchiveMailInfo *info) const { const int numberOfItem(mWidget.treeWidget->topLevelItemCount()); for (int i = 0; i < numberOfItem; ++i) { ArchiveMailItem *mailItem = static_cast(mWidget.treeWidget->topLevelItem(i)); ArchiveMailInfo *archiveItemInfo = mailItem->info(); if (archiveItemInfo) { if (info->saveCollectionId() == archiveItemInfo->saveCollectionId()) { return true; } } } return false; } void ArchiveMailWidget::slotOpenFolder() { const QList listItems = mWidget.treeWidget->selectedItems(); if (listItems.count() == 1) { QTreeWidgetItem *item = listItems.first(); if (!item) { return; } ArchiveMailItem *archiveItem = static_cast(item); ArchiveMailInfo *archiveItemInfo = archiveItem->info(); if (archiveItemInfo) { const QUrl url = archiveItemInfo->url(); KRun *runner = new KRun(url, parentWidget()); // will delete itself runner->setRunExecutables(false); } } } void ArchiveMailWidget::slotArchiveNow() { const QList listItems = mWidget.treeWidget->selectedItems(); if (listItems.count() == 1) { QTreeWidgetItem *item = listItems.first(); if (!item) { return; } ArchiveMailItem *archiveItem = static_cast(item); ArchiveMailInfo *archiveItemInfo = archiveItem->info(); save(); if (archiveItemInfo) { Q_EMIT archiveNow(archiveItemInfo); } } } void ArchiveMailWidget::slotItemChanged(QTreeWidgetItem *item, int col) { if (item) { ArchiveMailItem *archiveItem = static_cast(item); if (archiveItem->info()) { if (col == ArchiveMailWidget::Name) { archiveItem->info()->setEnabled(archiveItem->checkState(ArchiveMailWidget::Name) == Qt::Checked); mChanged = true; } else if (col == ArchiveMailWidget::NextArchive) { updateDiffDate(archiveItem, archiveItem->info()); } } } } QSize ArchiveMailWidget::restoreDialogSize() const { auto group = config()->group(myConfigGroupName); const QSize size = group.readEntry("Size", QSize(500, 300)); return size; } void ArchiveMailWidget::saveDialogSize(const QSize &size) { auto group = config()->group(myConfigGroupName); group.writeEntry("Size", size); } diff --git a/agents/followupreminderagent/followupreminderinfowidget.cpp b/agents/followupreminderagent/followupreminderinfowidget.cpp index c478e8e0e..e81f944c5 100644 --- a/agents/followupreminderagent/followupreminderinfowidget.cpp +++ b/agents/followupreminderagent/followupreminderinfowidget.cpp @@ -1,247 +1,247 @@ /* Copyright (C) 2014-2019 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 "followupreminderinfowidget.h" #include "FollowupReminder/FollowUpReminderInfo" #include "FollowupReminder/FollowUpReminderUtil" #include "jobs/followupremindershowmessagejob.h" #include "followupreminderagent_debug.h" #include #include #include #include #include #include #include #include #include #include // #define DEBUG_MESSAGE_ID namespace { inline QString followUpItemPattern() { return QStringLiteral("FollowupReminderItem \\d+"); } } FollowUpReminderInfoItem::FollowUpReminderInfoItem(QTreeWidget *parent) : QTreeWidgetItem(parent) , mInfo(nullptr) { } FollowUpReminderInfoItem::~FollowUpReminderInfoItem() { delete mInfo; } void FollowUpReminderInfoItem::setInfo(FollowUpReminder::FollowUpReminderInfo *info) { mInfo = info; } FollowUpReminder::FollowUpReminderInfo *FollowUpReminderInfoItem::info() const { return mInfo; } FollowUpReminderInfoWidget::FollowUpReminderInfoWidget(QWidget *parent) : QWidget(parent) , mChanged(false) { setObjectName(QStringLiteral("FollowUpReminderInfoWidget")); QHBoxLayout *hbox = new QHBoxLayout(this); - hbox->setMargin(0); + hbox->setContentsMargins(0, 0, 0, 0); mTreeWidget = new QTreeWidget(this); mTreeWidget->setObjectName(QStringLiteral("treewidget")); QStringList headers; headers << i18n("To") << i18n("Subject") << i18n("Dead Line") << i18n("Answer") #ifdef DEBUG_MESSAGE_ID << QStringLiteral("Message Id") << QStringLiteral("Answer Message Id") #endif ; mTreeWidget->setHeaderLabels(headers); mTreeWidget->setSortingEnabled(true); mTreeWidget->setRootIsDecorated(false); mTreeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); mTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(mTreeWidget, &QTreeWidget::customContextMenuRequested, this, &FollowUpReminderInfoWidget::slotCustomContextMenuRequested); hbox->addWidget(mTreeWidget); } FollowUpReminderInfoWidget::~FollowUpReminderInfoWidget() { } void FollowUpReminderInfoWidget::setInfo(const QList &infoList) { mTreeWidget->clear(); for (FollowUpReminder::FollowUpReminderInfo *info : infoList) { if (info->isValid()) { createOrUpdateItem(info); } } } void FollowUpReminderInfoWidget::load() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); const QStringList filterGroups = config->groupList().filter(QRegularExpression(followUpItemPattern())); const int numberOfItem = filterGroups.count(); for (int i = 0; i < numberOfItem; ++i) { KConfigGroup group = config->group(filterGroups.at(i)); FollowUpReminder::FollowUpReminderInfo *info = new FollowUpReminder::FollowUpReminderInfo(group); if (info->isValid()) { createOrUpdateItem(info); } else { delete info; } } } QList FollowUpReminderInfoWidget::listRemoveId() const { return mListRemoveId; } void FollowUpReminderInfoWidget::createOrUpdateItem(FollowUpReminder::FollowUpReminderInfo *info, FollowUpReminderInfoItem *item) { if (!item) { item = new FollowUpReminderInfoItem(mTreeWidget); } item->setInfo(info); item->setText(To, info->to()); item->setText(Subject, info->subject()); const QString date = QLocale().toString(info->followUpReminderDate()); item->setText(DeadLine, date); const bool answerWasReceived = info->answerWasReceived(); item->setText(AnswerWasReceived, answerWasReceived ? i18n("Received") : i18n("On hold")); item->setData(0, AnswerItemFound, answerWasReceived); if (answerWasReceived) { - item->setBackgroundColor(DeadLine, Qt::green); + item->setBackground(DeadLine, Qt::green); } else { if (info->followUpReminderDate() < QDate::currentDate()) { - item->setBackgroundColor(DeadLine, Qt::red); + item->setBackground(DeadLine, Qt::red); } } #ifdef DEBUG_MESSAGE_ID item->setText(MessageId, QString::number(info->originalMessageItemId())); item->setText(AnswerMessageId, QString::number(info->answerMessageItemId())); #endif } bool FollowUpReminderInfoWidget::save() const { if (!mChanged) { return false; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); // first, delete all filter groups: const QStringList filterGroups = config->groupList().filter(QRegularExpression(followUpItemPattern())); for (const QString &group : filterGroups) { config->deleteGroup(group); } const int numberOfItem(mTreeWidget->topLevelItemCount()); int i = 0; for (; i < numberOfItem; ++i) { FollowUpReminderInfoItem *mailItem = static_cast(mTreeWidget->topLevelItem(i)); if (mailItem->info()) { KConfigGroup group = config->group(FollowUpReminder::FollowUpReminderUtil::followUpReminderPattern().arg(i)); mailItem->info()->writeConfig(group, i); } } ++i; KConfigGroup general = config->group(QStringLiteral("General")); general.writeEntry("Number", i); config->sync(); return true; } void FollowUpReminderInfoWidget::slotCustomContextMenuRequested(const QPoint &pos) { Q_UNUSED(pos); const QList listItems = mTreeWidget->selectedItems(); const int nbElementSelected = listItems.count(); if (nbElementSelected > 0) { QMenu menu(this); QAction *showMessage = nullptr; FollowUpReminderInfoItem *mailItem = nullptr; if ((nbElementSelected == 1)) { mailItem = static_cast(listItems.at(0)); if (mailItem->data(0, AnswerItemFound).toBool()) { showMessage = menu.addAction(i18n("Show Message")); menu.addSeparator(); } } QAction *deleteItem = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete")); QAction *result = menu.exec(QCursor::pos()); if (result) { if (result == showMessage) { openShowMessage(mailItem->info()->answerMessageItemId()); } else if (result == deleteItem) { removeItem(listItems); } } } } void FollowUpReminderInfoWidget::openShowMessage(Akonadi::Item::Id id) { FollowUpReminderShowMessageJob *job = new FollowUpReminderShowMessageJob(id); job->start(); } void FollowUpReminderInfoWidget::removeItem(const QList &mailItemLst) { if (mailItemLst.isEmpty()) { qCDebug(FOLLOWUPREMINDERAGENT_LOG) << "Not item selected"; } else { if (KMessageBox::Yes == KMessageBox::warningYesNo(this, i18np("Do you want to remove this selected item?", "Do you want to remove these %1 selected items?", mailItemLst.count()), i18n("Delete"))) { for (QTreeWidgetItem *item : mailItemLst) { FollowUpReminderInfoItem *mailItem = static_cast(item); mListRemoveId << mailItem->info()->uniqueIdentifier(); delete mailItem; } mChanged = true; } } } void FollowUpReminderInfoWidget::restoreTreeWidgetHeader(const QByteArray &data) { mTreeWidget->header()->restoreState(data); } void FollowUpReminderInfoWidget::saveTreeWidgetHeader(KConfigGroup &group) { group.writeEntry("HeaderState", mTreeWidget->header()->saveState()); } diff --git a/agents/mailfilteragent/filterlogdialog.cpp b/agents/mailfilteragent/filterlogdialog.cpp index 21d2845fd..1478fd481 100644 --- a/agents/mailfilteragent/filterlogdialog.cpp +++ b/agents/mailfilteragent/filterlogdialog.cpp @@ -1,400 +1,400 @@ /* Copyright (c) 2003 Andreas Gungl Copyright (C) 2012-2019 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); + pageVBoxLayout->setContentsMargins(0, 0, 0, 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); + hboxHBoxLayout->setContentsMargins(0, 0, 0, 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/ktnef/src/attachpropertydialog.cpp b/ktnef/src/attachpropertydialog.cpp index 9cc88eeb0..448cbe644 100644 --- a/ktnef/src/attachpropertydialog.cpp +++ b/ktnef/src/attachpropertydialog.cpp @@ -1,253 +1,253 @@ /* This file is part of KTnef. Copyright (C) 2002 Michael Goffioul Copyright (c) 2012 Allen Winter 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. 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 "attachpropertydialog.h" #include "qwmf.h" #include #include #include #include #include "ktnef_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include AttachPropertyDialog::AttachPropertyDialog(QWidget *parent) : QDialog(parent) , mAttach(nullptr) { setModal(true); QVBoxLayout *mainLayout = new QVBoxLayout(this); QWidget *mainWidget = new QWidget(this); mUI.setupUi(mainWidget); mUI.mProperties->setHeaderHidden(true); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); QPushButton *user1Button = new QPushButton; buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); connect(buttonBox, &QDialogButtonBox::rejected, this, &AttachPropertyDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); user1Button->setText(i18n("Save...")); connect(user1Button, &QPushButton::clicked, this, &AttachPropertyDialog::slotSave); mainLayout->addWidget(mainWidget); mainLayout->addWidget(buttonBox); readConfig(); } AttachPropertyDialog::~AttachPropertyDialog() { writeConfig(); } void AttachPropertyDialog::readConfig() { KConfigGroup group(KSharedConfig::openConfig(), "AttachPropertyDialog"); const QSize size = group.readEntry("Size", QSize(500, 400)); if (size.isValid()) { resize(size); } } void AttachPropertyDialog::writeConfig() { KConfigGroup group(KSharedConfig::openConfig(), "AttachPropertyDialog"); group.writeEntry("Size", size()); group.sync(); } void AttachPropertyDialog::setAttachment(KTNEFAttach *attach) { QString s = attach->fileName().isEmpty() ? attach->name() : attach->fileName(); mUI.mFilename->setText(QLatin1String("") + s + QLatin1String("")); setWindowTitle(i18nc("@title:window", "Properties for Attachment %1", s)); mUI.mDisplay->setText(attach->displayName()); mUI.mMime->setText(attach->mimeTag()); s.setNum(attach->size()); s.append(i18n(" bytes")); mUI.mSize->setText(s); QMimeDatabase db; QMimeType mimetype = db.mimeTypeForName(attach->mimeTag()); - QPixmap pix = loadRenderingPixmap(attach, qApp->palette().color(QPalette::Background)); + QPixmap pix = loadRenderingPixmap(attach, qApp->palette().color(QPalette::Window)); if (!pix.isNull()) { mUI.mIcon->setPixmap(pix); } else { mUI.mIcon->setPixmap(mimetype.iconName()); } mUI.mDescription->setText(mimetype.comment()); s.setNum(attach->index()); mUI.mIndex->setText(s); formatPropertySet(attach, mUI.mProperties); mAttach = attach; } void AttachPropertyDialog::slotSave() { if (saveProperty(mUI.mProperties, mAttach, this)) { accept(); } } void AttachPropertyDialog::formatProperties(const QMap &props, QTreeWidget *lv, QTreeWidgetItem *item, const QString &prefix) { QMap::ConstIterator end(props.constEnd()); for (QMap::ConstIterator it = props.begin(); it != end; ++it) { QTreeWidgetItem *newItem = nullptr; if (lv) { newItem = new QTreeWidgetItem(lv, QStringList((*it)->keyString())); } else if (item) { newItem = new QTreeWidgetItem(item, QStringList((*it)->keyString())); } else { qCWarning(KTNEFAPPS_LOG) << "formatProperties() called with no listview and no item"; return; } QVariant value = (*it)->value(); if (value.type() == QVariant::List) { newItem->setExpanded(true); newItem->setText(0, newItem->text(0) +QLatin1String(" [") + QString::number(value.toList().count()) + QLatin1Char(']')); int i = 0; QList::ConstIterator litEnd = value.toList().constEnd(); for (QList::ConstIterator lit = value.toList().constBegin(); lit != litEnd; ++lit, ++i) { new QTreeWidgetItem(newItem, QStringList() << QLatin1Char('[') + QString::number(i) + QLatin1Char(']') << QString(KTNEFProperty::formatValue(*lit))); } } else if (value.type() == QVariant::DateTime) { newItem->setText(1, value.toDateTime().toString()); } else { newItem->setText(1, (*it)->valueString()); newItem->setText(2, prefix + QLatin1Char('_') + QString::number(it.key())); } } } void AttachPropertyDialog::formatPropertySet(KTNEFPropertySet *pSet, QTreeWidget *lv) { formatProperties(pSet->properties(), lv, nullptr, QStringLiteral("prop")); QTreeWidgetItem *item = new QTreeWidgetItem(lv, QStringList(i18nc("@label", "TNEF Attributes"))); item->setExpanded(true); formatProperties(pSet->attributes(), nullptr, item, QStringLiteral("attr")); } bool AttachPropertyDialog::saveProperty(QTreeWidget *lv, KTNEFPropertySet *pSet, QWidget *parent) { QList list = lv->selectedItems(); if (list.isEmpty()) { KMessageBox::error( parent, i18nc("@info", "Must select an item first.")); return false; } QTreeWidgetItem *item = list.first(); if (item->text(2).isEmpty()) { KMessageBox::error( parent, i18nc("@info", "The selected item cannot be saved because it has an empty tag.")); } else { QString tag = item->text(2); int key = tag.midRef(5).toInt(); QVariant prop = (tag.startsWith(QLatin1String("attr_")) ? pSet->attribute(key) : pSet->property(key)); QString filename = QFileDialog::getSaveFileName(parent, QString(), tag, QString()); if (!filename.isEmpty()) { QFile f(filename); if (f.open(QIODevice::WriteOnly)) { switch (prop.type()) { case QVariant::ByteArray: f.write(prop.toByteArray().data(), prop.toByteArray().size()); break; default: { QTextStream t(&f); t << prop.toString(); break; } } f.close(); } else { KMessageBox::error( parent, i18nc("@info", "Unable to open file for writing, check file permissions.")); } } } return true; } QPixmap AttachPropertyDialog::loadRenderingPixmap(KTNEFPropertySet *pSet, const QColor &bgColor) { QPixmap pix; QVariant rendData = pSet->attribute(attATTACHRENDDATA); QVariant wmf = pSet->attribute(attATTACHMETAFILE); if (!rendData.isNull() && !wmf.isNull()) { // Get rendering size QByteArray qb = rendData.toByteArray(); QBuffer rendBuffer(&qb); rendBuffer.open(QIODevice::ReadOnly); QDataStream rendStream(&rendBuffer); rendStream.setByteOrder(QDataStream::LittleEndian); quint16 type, w, h; rendStream >> type >> w >> w; // read type and skip 4 bytes rendStream >> w >> h; rendBuffer.close(); if (type == 1 && w > 0 && h > 0) { // Load WMF data QWinMetaFile wmfLoader; QByteArray qb = wmf.toByteArray(); QBuffer wmfBuffer(&qb); wmfBuffer.open(QIODevice::ReadOnly); if (wmfLoader.load(wmfBuffer)) { pix.scaled(w, h, Qt::KeepAspectRatio); pix.fill(bgColor); wmfLoader.paint(&pix); } wmfBuffer.close(); } } return pix; } diff --git a/ktnef/src/ktnefview.cpp b/ktnef/src/ktnefview.cpp index 5d806b30e..c3736a5d9 100644 --- a/ktnef/src/ktnefview.cpp +++ b/ktnef/src/ktnefview.cpp @@ -1,153 +1,153 @@ /* This file is part of KTnef. Copyright (C) 2002 Michael Goffioul Copyright (c) 2012 Allen Winter 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. 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 "ktnefview.h" #include "attachpropertydialog.h" #include #include #include #include #include #include #include class Attachment : public QTreeWidgetItem { public: Attachment(QTreeWidget *parent, KTNEFAttach *attach); ~Attachment(); KTNEFAttach *getAttachment() const { return mAttach; } private: KTNEFAttach *mAttach = nullptr; }; Attachment::Attachment(QTreeWidget *parent, KTNEFAttach *attach) : QTreeWidgetItem(parent, QStringList(attach->name())) , mAttach(attach) { setText(2, QString::number(mAttach->size())); if (!mAttach->fileName().isEmpty()) { setText(0, mAttach->fileName()); } QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mAttach->mimeTag()); setText(1, mimeType.comment()); - QPixmap pix = AttachPropertyDialog::loadRenderingPixmap(attach, qApp->palette().color(QPalette::Background)); + QPixmap pix = AttachPropertyDialog::loadRenderingPixmap(attach, qApp->palette().color(QPalette::Window)); if (!pix.isNull()) { setIcon(0, pix); } else { setIcon(0, QIcon::fromTheme(mimeType.iconName())); } } Attachment::~Attachment() { } //----------------------------------------------------------------------------// KTNEFView::KTNEFView(QWidget *parent) : QTreeWidget(parent) { const QStringList headerLabels = (QStringList(i18nc("@title:column file name", "File Name")) << i18nc("@title:column file type", "File Type") << i18nc("@title:column file size", "Size")); setHeaderLabels(headerLabels); setSelectionMode(QAbstractItemView::ExtendedSelection); setDragEnabled(true); setSortingEnabled(true); QTimer::singleShot(0, this, &KTNEFView::adjustColumnWidth); } KTNEFView::~KTNEFView() { } void KTNEFView::setAttachments(const QList &list) { clear(); if (!list.isEmpty()) { QList::ConstIterator it; QList::ConstIterator end(list.constEnd()); for (it = list.constBegin(); it != end; ++it) { new Attachment(this, (*it)); } } } void KTNEFView::resizeEvent(QResizeEvent *e) { adjustColumnWidth(); resize(width(), height()); if (e) { QTreeWidget::resizeEvent(e); } } QList KTNEFView::getSelection() { mAttachments.clear(); QList list = selectedItems(); if (list.isEmpty() || !list.first()) { return mAttachments; } QList::const_iterator it; QList::const_iterator end(list.constEnd()); mAttachments.reserve(list.count()); for (it = list.constBegin(); it != end; ++it) { Attachment *a = static_cast(*it); mAttachments.append(a->getAttachment()); } return mAttachments; } void KTNEFView::startDrag(Qt::DropActions dropAction) { Q_UNUSED(dropAction); QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); QList list; while (*it) { Attachment *a = static_cast(*it); list << a->getAttachment(); ++it; } if (!list.isEmpty()) { Q_EMIT dragRequested(list); } } void KTNEFView::adjustColumnWidth() { const int w = width() / 2; setColumnWidth(0, w); setColumnWidth(1, w / 2); setColumnWidth(2, w / 2); } diff --git a/ktnef/src/qwmf.cpp b/ktnef/src/qwmf.cpp index b55ef8d20..1b5ee9e83 100644 --- a/ktnef/src/qwmf.cpp +++ b/ktnef/src/qwmf.cpp @@ -1,1257 +1,1258 @@ /* Windows Meta File Loader/Painter Class Implementation * * Copyright ( C ) 1998 Stefan Taferner * Modified 2002 thierry lorthiois * * 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 * MERCHANTABLILITY 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 // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include "ktnef_debug.h" bool qwmfDebug = false; #include "qwmf.h" #include "wmfstruct.h" #include "metafuncs.h" #define QWMF_DEBUG 0 class WmfCmd { public: ~WmfCmd() { delete next; } WmfCmd *next; unsigned short funcIndex; long numParm; short *parm; }; class WinObjHandle { public: virtual ~WinObjHandle() { } virtual void apply(QPainter &p) = 0; }; class WinObjBrushHandle : public WinObjHandle { public: void apply(QPainter &p) override; QBrush brush; virtual ~WinObjBrushHandle() { } }; class WinObjPenHandle : public WinObjHandle { public: void apply(QPainter &p) override; QPen pen; virtual ~WinObjPenHandle() { } }; class WinObjPatternBrushHandle : public WinObjHandle { public: void apply(QPainter &p) override; QBrush brush; QImage image; virtual ~WinObjPatternBrushHandle() { } }; class WinObjFontHandle : public WinObjHandle { public: void apply(QPainter &p) override; QFont font; int rotation; virtual ~WinObjFontHandle() { } }; void WinObjBrushHandle::apply(QPainter &p) { p.setBrush(brush); } void WinObjPenHandle::apply(QPainter &p) { p.setPen(pen); } void WinObjPatternBrushHandle::apply(QPainter &p) { p.setBrush(brush); } void WinObjFontHandle::apply(QPainter &p) { p.setFont(font); } #define MAX_OBJHANDLE 64 //----------------------------------------------------------------------------- QWinMetaFile::QWinMetaFile() { mValid = false; mFirstCmd = nullptr; mObjHandleTab = nullptr; mDpi = 1000; } //----------------------------------------------------------------------------- QWinMetaFile::~QWinMetaFile() { delete mFirstCmd; if (mObjHandleTab) { delete[] mObjHandleTab; } } //----------------------------------------------------------------------------- bool QWinMetaFile::load(const QString &filename) { QFile file(filename); if (!file.exists()) { qCDebug(KTNEFAPPS_LOG) << "File" << QFile::encodeName(filename) << " does not exist"; return false; } if (!file.open(QIODevice::ReadOnly)) { qCDebug(KTNEFAPPS_LOG) << "Cannot open file" << QFile::encodeName(filename); return false; } QByteArray ba = file.readAll(); file.close(); QBuffer buffer(&ba); buffer.open(QIODevice::ReadOnly); return load(buffer); } //----------------------------------------------------------------------------- bool QWinMetaFile::load(QBuffer &buffer) { QDataStream st; WmfEnhMetaHeader eheader; WmfMetaHeader header; WmfPlaceableHeader pheader; WORD checksum; int filePos, idx, i; WmfCmd *cmd, *last; DWORD rdSize; WORD rdFunc; mTextAlign = 0; mRotation = 0; mTextColor = Qt::black; if (mFirstCmd) { delete mFirstCmd; } mFirstCmd = nullptr; st.setDevice(&buffer); st.setByteOrder(QDataStream::LittleEndian); // Great, I love Qt ! //----- Read placeable metafile header st >> pheader.key; mIsPlaceable = (pheader.key == (DWORD)APMHEADER_KEY); if (mIsPlaceable) { st >> pheader.hmf; st >> pheader.bbox.left; st >> pheader.bbox.top; st >> pheader.bbox.right; st >> pheader.bbox.bottom; st >> pheader.inch; st >> pheader.reserved; st >> pheader.checksum; checksum = calcCheckSum(&pheader); if (pheader.checksum != checksum) { mIsPlaceable = false; } mDpi = pheader.inch; mBBox.setLeft(pheader.bbox.left); mBBox.setTop(pheader.bbox.top); mBBox.setRight(pheader.bbox.right); mBBox.setBottom(pheader.bbox.bottom); mHeaderBoundingBox = mBBox; if (QWMF_DEBUG) { qCDebug(KTNEFAPPS_LOG) << endl << "-------------------------------------------------"; qCDebug(KTNEFAPPS_LOG) << "WMF Placeable Header (" << static_cast(sizeof(pheader)) << "):"; qCDebug(KTNEFAPPS_LOG) << " bbox=(" << mBBox.left() << ";" << mBBox.top() << ";" << mBBox.width() << "; " << mBBox.height() << ")"; qCDebug(KTNEFAPPS_LOG) << " inch=" << pheader.inch; qCDebug(KTNEFAPPS_LOG) << " checksum=" << pheader.checksum << "(" << (pheader.checksum == checksum ? "ok" : "wrong") << " )"; } } else { buffer.reset(); } //----- Read as enhanced metafile header filePos = buffer.pos(); st >> eheader.iType; st >> eheader.nSize; st >> eheader.rclBounds.left; st >> eheader.rclBounds.top; st >> eheader.rclBounds.right; st >> eheader.rclBounds.bottom; st >> eheader.rclFrame.left; st >> eheader.rclFrame.top; st >> eheader.rclFrame.right; st >> eheader.rclFrame.bottom; st >> eheader.dSignature; mIsEnhanced = (eheader.dSignature == ENHMETA_SIGNATURE); if (mIsEnhanced) { // is it really enhanced ? st >> eheader.nVersion; st >> eheader.nBytes; st >> eheader.nRecords; st >> eheader.nHandles; st >> eheader.sReserved; st >> eheader.nDescription; st >> eheader.offDescription; st >> eheader.nPalEntries; st >> eheader.szlDevice.width; st >> eheader.szlDevice.height; st >> eheader.szlMillimeters.width; st >> eheader.szlMillimeters.height; if (QWMF_DEBUG) { qCDebug(KTNEFAPPS_LOG) << endl << "-------------------------------------------------"; qCDebug(KTNEFAPPS_LOG) << "WMF Extended Header:"; qCDebug(KTNEFAPPS_LOG) << " iType=" << eheader.iType; qCDebug(KTNEFAPPS_LOG) << " nSize=" << eheader.nSize; qCDebug(KTNEFAPPS_LOG) << " rclBounds=(" << eheader.rclBounds.left << ";" << eheader.rclBounds.top << ";" << eheader.rclBounds.right << "; " << eheader.rclBounds.bottom << ")"; qCDebug(KTNEFAPPS_LOG) << " rclFrame=(" << eheader.rclFrame.left << ";" << eheader.rclFrame.top << ";" << eheader.rclFrame.right << "; " << eheader.rclFrame.bottom << ")"; qCDebug(KTNEFAPPS_LOG) << " nBytes=" << eheader.nBytes; qCDebug(KTNEFAPPS_LOG) << "\nNOT YET IMPLEMENTED, SORRY."; } } else { // no, not enhanced //----- Read as standard metafile header buffer.seek(filePos); st >> header.mtType; st >> header.mtHeaderSize; st >> header.mtVersion; st >> header.mtSize; st >> header.mtNoObjects; st >> header.mtMaxRecord; st >> header.mtNoParameters; if (QWMF_DEBUG) { qCDebug(KTNEFAPPS_LOG) << "WMF Header:" << "mtSize=" << header.mtSize; } } //----- Test header validity mValid = ((header.mtHeaderSize == 9) && (header.mtNoParameters == 0)) || mIsEnhanced || mIsPlaceable; if (mValid) { //----- Read Metafile Records last = nullptr; rdFunc = -1; while (!st.atEnd() && (rdFunc != 0)) { st >> rdSize; st >> rdFunc; idx = findFunc(rdFunc); rdSize -= 3; cmd = new WmfCmd; cmd->next = nullptr; if (last) { last->next = cmd; } else { mFirstCmd = cmd; } cmd->funcIndex = idx; cmd->numParm = rdSize; cmd->parm = new WORD[ rdSize ]; last = cmd; for (i = 0; i < rdSize && !st.atEnd(); ++i) { st >> cmd->parm[ i ]; } if (rdFunc == 0x020B) { // SETWINDOWORG: dimensions mBBox.setLeft(cmd->parm[ 1 ]); mBBox.setTop(cmd->parm[ 0 ]); } if (rdFunc == 0x020C) { // SETWINDOWEXT: dimensions mBBox.setWidth(cmd->parm[ 1 ]); mBBox.setHeight(cmd->parm[ 0 ]); } if (i < rdSize) { qCDebug(KTNEFAPPS_LOG) << "WMF : file truncated !"; return false; } } //----- Test records validities mValid = (rdFunc == 0) && (mBBox.width() != 0) && (mBBox.height() != 0); if (!mValid) { qCDebug(KTNEFAPPS_LOG) << "WMF : incorrect file format !"; } } else { qCDebug(KTNEFAPPS_LOG) << "WMF Header : incorrect header !"; } buffer.close(); return mValid; } //----------------------------------------------------------------------------- bool QWinMetaFile::paint(QPaintDevice *aTarget, bool absolute) { int idx, i; WmfCmd *cmd; if (!mValid) { return false; } assert(aTarget != nullptr); if (mPainter.isActive()) { return false; } if (mObjHandleTab) { delete[] mObjHandleTab; } mObjHandleTab = new WinObjHandle * [ MAX_OBJHANDLE ]; for (i = MAX_OBJHANDLE - 1; i >= 0; --i) { mObjHandleTab[ i ] = nullptr; } - mPainter.resetMatrix(); + mPainter.resetTransform(); mWinding = false; mAbsoluteCoord = absolute; mPainter.begin(aTarget); if (QWMF_DEBUG) { qCDebug(KTNEFAPPS_LOG) << "Bounding box :" << mBBox.left() << " " << mBBox.top() << " " << mBBox.right() << " " << mBBox.bottom(); } if (mAbsoluteCoord) { mPainter.setWindow(mBBox.top(), mBBox.left(), mBBox.width(), mBBox.height()); } mInternalWorldMatrix.reset(); for (cmd = mFirstCmd; cmd; cmd = cmd->next) { idx = cmd->funcIndex; (this->*metaFuncTab[ idx ].method)(cmd->numParm, cmd->parm); if (QWMF_DEBUG) { QString str, param; if (metaFuncTab[ idx ].name == nullptr) { str += QLatin1String("UNKNOWN "); } if (metaFuncTab[ idx ].method == &QWinMetaFile::noop) { str += QLatin1String("UNIMPLEMENTED "); } str += QLatin1String(metaFuncTab[ idx ].name); str += QLatin1String(" : "); for (i = 0; i < cmd->numParm; ++i) { param.setNum(cmd->parm[ i ]); str += param; str += QLatin1Char(' '); } qCDebug(KTNEFAPPS_LOG) << str; } } /* // TODO: cleanup this code when QPicture::setBoundingBox() is possible in KOClipart (QT31) // because actually QPicture::boundingBox() != mBBox() mWindowsCoord += 1; if ( mWindowsCoord == 2 ) { qCDebug(KTNEFAPPS_LOG) <<"DRAW ANGLES"; mPainter.setPen( Qt::white ); mPainter.drawPoint( mBBox.left(), mBBox.top() ); mPainter.drawPoint( mBBox.right(), mBBox.bottom() ); } */ mPainter.end(); return true; } //----------------s------------------------------------------------------------- // Metafile painter methods //----------------------------------------------------------------------------- void QWinMetaFile::setWindowOrg(long, short *parm) { if (mAbsoluteCoord) { QRect r = mPainter.window(); mPainter.setWindow(parm[ 1 ], parm[ 0 ], r.width(), r.height()); } else { double dx = mInternalWorldMatrix.dx(); double dy = mInternalWorldMatrix.dy(); mInternalWorldMatrix.translate(-dx, -dy); mInternalWorldMatrix.translate(-parm[ 1 ], -parm[ 0 ]); mPainter.translate(-dx, -dy); mPainter.translate(-parm[ 1 ], -parm[ 0 ]); } } //----------------------------------------------------------------------------- void QWinMetaFile::setWindowExt(long, short *parm) { // negative value allowed for width and height : QABS() forbidden if (mAbsoluteCoord) { QRect r = mPainter.window(); mPainter.setWindow(r.left(), r.top(), parm[ 1 ], parm[ 0 ]); } else { if ((parm[ 0 ] != 0) && (parm[ 1 ] != 0)) { QRect r = mPainter.window(); double dx = mInternalWorldMatrix.dx(); double dy = mInternalWorldMatrix.dy(); double sx = mInternalWorldMatrix.m11(); double sy = mInternalWorldMatrix.m22(); mInternalWorldMatrix.translate(-dx, -dy); mInternalWorldMatrix.scale(1 / sx, 1 / sy); mPainter.translate(-dx, -dy); mPainter.scale(1 / sx, 1 / sy); sx = (double)r.width() / (double)parm[ 1 ]; sy = (double)r.height() / (double)parm[ 0 ]; mInternalWorldMatrix.scale(sx, sy); mInternalWorldMatrix.translate(dx, dy); mPainter.scale(sx, sy); mPainter.translate(dx, dy); } } } //----------------------------------------------------------------------------- // Drawing //----------------------------------------------------------------------------- void QWinMetaFile::lineTo(long, short *parm) { mPainter.drawLine(mLastPos, QPoint(parm[1], parm[0])); } //----------------------------------------------------------------------------- void QWinMetaFile::moveTo(long, short *parm) { mLastPos = QPoint(parm[ 1 ], parm[ 0 ]); } //----------------------------------------------------------------------------- void QWinMetaFile::ellipse(long, short *parm) { mPainter.drawEllipse(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]); } //----------------------------------------------------------------------------- void QWinMetaFile::polygon(long, short *parm) { QPolygon *pa; // causing a memleck ??? pa = pointArray(parm[ 0 ], &parm[ 1 ]); if (mWinding) { mPainter.drawPolygon(*pa, Qt::WindingFill); } else { mPainter.drawPolygon(*pa, Qt::OddEvenFill); } delete pa; } //----------------------------------------------------------------------------- void QWinMetaFile::polyPolygon(long, short *parm) { QRegion region; int i, j, startPolygon; mPainter.save(); // define clipping region QRect win = bbox(); startPolygon = 1 + parm[ 0 ]; for (i = 0; i < parm[ 0 ]; ++i) { QPolygon pa1(parm[ 1 + i ]); for (j = 0; j < parm[ 1 + i ]; ++j) { pa1.setPoint(j, parm[ startPolygon ], parm[ startPolygon + 1 ]); startPolygon += 2; } QRegion r(pa1); region = region.xored(r); } mPainter.setClipRegion(region); // fill polygons mPainter.fillRect(win.left(), win.top(), win.width(), win.height(), mPainter.brush()); // draw polygon's border if necessary if (mPainter.pen().style() != Qt::NoPen) { mPainter.setClipping(false); mPainter.setBrush(Qt::NoBrush); QPolygon *pa; int idxPolygon = 1 + parm[ 0 ]; for (i = 0; i < parm[ 0 ]; ++i) { pa = pointArray(parm[ 1 + i ], &parm[ idxPolygon ]); mPainter.drawPolygon(*pa); idxPolygon += parm[ 1 + i ] * 2; } } mPainter.restore(); } //----------------------------------------------------------------------------- void QWinMetaFile::polyline(long, short *parm) { QPolygon *pa; pa = pointArray(parm[ 0 ], &parm[ 1 ]); mPainter.drawPolyline(*pa); } //----------------------------------------------------------------------------- void QWinMetaFile::rectangle(long, short *parm) { mPainter.drawRect(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]); } //----------------------------------------------------------------------------- void QWinMetaFile::roundRect(long, short *parm) { int xRnd = 0, yRnd = 0; // convert (xRound, yRound) in percentage if ((parm[ 3 ] - parm[ 5 ]) != 0) { xRnd = (parm[ 1 ] * 100) / (parm[ 3 ] - parm[ 5 ]); } if ((parm[ 2 ] - parm[ 4 ]) != 0) { yRnd = (parm[ 0 ] * 100) / (parm[ 2 ] - parm[ 4 ]); } - mPainter.drawRoundRect(parm[ 5 ], parm[ 4 ], parm[ 3 ] - parm[ 5 ], parm[ 2 ] - parm[ 4 ], xRnd, yRnd); + mPainter.drawRoundedRect(parm[ 5 ], parm[ 4 ], parm[ 3 ] - parm[ 5 ], parm[ 2 ] - parm[ 4 ], + xRnd, yRnd, Qt::RelativeSize); } //----------------------------------------------------------------------------- void QWinMetaFile::arc(long, short *parm) { int xCenter, yCenter, angleStart, aLength; xCenter = parm[ 7 ] + ((parm[ 5 ] - parm[ 7 ]) / 2); yCenter = parm[ 6 ] + ((parm[ 4 ] - parm[ 6 ]) / 2); xyToAngle(parm[ 3 ] - xCenter, yCenter - parm[ 2 ], parm[ 1 ] - xCenter, yCenter - parm[ 0 ], angleStart, aLength); mPainter.drawArc(parm[ 7 ], parm[ 6 ], parm[ 5 ] - parm[ 7 ], parm[ 4 ] - parm[ 6 ], angleStart, aLength); } //----------------------------------------------------------------------------- void QWinMetaFile::chord(long, short *parm) { int xCenter, yCenter, angleStart, aLength; xCenter = parm[ 7 ] + ((parm[ 5 ] - parm[ 7 ]) / 2); yCenter = parm[ 6 ] + ((parm[ 4 ] - parm[ 6 ]) / 2); xyToAngle(parm[ 3 ] - xCenter, yCenter - parm[ 2 ], parm[ 1 ] - xCenter, yCenter - parm[ 0 ], angleStart, aLength); mPainter.drawChord(parm[ 7 ], parm[ 6 ], parm[ 5 ] - parm[ 7 ], parm[ 4 ] - parm[ 6 ], angleStart, aLength); } //----------------------------------------------------------------------------- void QWinMetaFile::pie(long, short *parm) { int xCenter, yCenter, angleStart, aLength; xCenter = parm[ 7 ] + ((parm[ 5 ] - parm[ 7 ]) / 2); yCenter = parm[ 6 ] + ((parm[ 4 ] - parm[ 6 ]) / 2); xyToAngle(parm[ 3 ] - xCenter, yCenter - parm[ 2 ], parm[ 1 ] - xCenter, yCenter - parm[ 0 ], angleStart, aLength); mPainter.drawPie(parm[ 7 ], parm[ 6 ], parm[ 5 ] - parm[ 7 ], parm[ 4 ] - parm[ 6 ], angleStart, aLength); } //----------------------------------------------------------------------------- void QWinMetaFile::setPolyFillMode(long, short *parm) { mWinding = parm[ 0 ]; } //----------------------------------------------------------------------------- void QWinMetaFile::setBkColor(long, short *parm) { mPainter.setBackground(QBrush(color(parm))); } //----------------------------------------------------------------------------- void QWinMetaFile::setBkMode(long, short *parm) { if (parm[ 0 ] == 1) { mPainter.setBackgroundMode(Qt::TransparentMode); } else { mPainter.setBackgroundMode(Qt::OpaqueMode); } } //----------------------------------------------------------------------------- void QWinMetaFile::setPixel(long, short *parm) { QPen pen = mPainter.pen(); mPainter.setPen(color(parm)); mPainter.drawPoint(parm[ 3 ], parm[ 2 ]); mPainter.setPen(pen); } //----------------------------------------------------------------------------- void QWinMetaFile::setRop(long, short *parm) { mPainter.setCompositionMode(winToQtComposition(parm[ 0 ])); } //----------------------------------------------------------------------------- void QWinMetaFile::saveDC(long, short *) { mPainter.save(); } //----------------------------------------------------------------------------- void QWinMetaFile::restoreDC(long, short *parm) { for (int i = 0; i > parm[ 0 ]; i--) { mPainter.restore(); } } //----------------------------------------------------------------------------- void QWinMetaFile::intersectClipRect(long, short *parm) { /* TODO: better implementation : need QT 3.0.2 QRegion region = mPainter.clipRegion(); if ( region.isEmpty() ) region = bbox(); */ QRegion region(bbox()); QRegion newRegion(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]); region = region.intersected(newRegion); mPainter.setClipRegion(region); } //----------------------------------------------------------------------------- void QWinMetaFile::excludeClipRect(long, short *parm) { /* TODO: better implementation : need QT 3.0.2 QRegion region = mPainter.clipRegion(); if ( region.isEmpty() ) region = bbox(); */ QRegion region(bbox()); QRegion newRegion(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]); region = region.subtracted(newRegion); mPainter.setClipRegion(region); } //----------------------------------------------------------------------------- // Text //----------------------------------------------------------------------------- void QWinMetaFile::setTextColor(long, short *parm) { mTextColor = color(parm); } //----------------------------------------------------------------------------- void QWinMetaFile::setTextAlign(long, short *parm) { mTextAlign = parm[ 0 ]; } //----------------------------------------------------------------------------- void QWinMetaFile::textOut(long num, short *parm) { short *copyParm = new short[ num + 1 ]; // re-order parameters int idxOffset = (parm[ 0 ] / 2) + 1 + (parm[ 0 ] & 1); copyParm[ 0 ] = parm[ idxOffset ]; copyParm[ 1 ] = parm[ idxOffset + 1 ]; copyParm[ 2 ] = parm[ 0 ]; copyParm[ 3 ] = 0; memcpy(©Parm[ 4 ], &parm[ 1 ], parm[ 0 ]); extTextOut(num + 1, copyParm); delete [] copyParm; } //----------------------------------------------------------------------------- void QWinMetaFile::extTextOut(long num, short *parm) { char *ptStr; int x, y, width, height; int idxOffset; if (parm[ 3 ] != 0) { // ETO_CLIPPED flag add 4 parameters ptStr = (char *)&parm[ 8 ]; } else { ptStr = (char *)&parm[ 4 ]; } QByteArray text(ptStr, parm[ 2 ] + 1); QFontMetrics fm(mPainter.font()); width = fm.boundingRect(QLatin1String(text)).width() + fm.descent(); // because fm.width(text) isn't rigth with Italic text height = fm.height(); mPainter.save(); if (mTextAlign & 0x01) { // (left, top) position = current logical position x = mLastPos.x(); y = mLastPos.y(); } else { // (left, top) position = parameters x = parm[ 1 ]; y = parm[ 0 ]; } if (mRotation) { mPainter.translate(parm[ 1 ], parm[ 0 ]); mPainter.rotate(mRotation); mPainter.translate(-parm[ 1 ], -parm[ 0 ]); } // alignment if (mTextAlign & 0x06) { x -= (width / 2); } if (mTextAlign & 0x08) { y -= (height - fm.descent()); } mPainter.setPen(mTextColor); idxOffset = (parm[ 2 ] / 2) + 4 + (parm[ 2 ] & 1); if ((parm[ 2 ] > 1) && (num >= (idxOffset + parm[ 2 ])) && (parm[ 3 ] == 0)) { // offset for each char int left = x; mPainter.drawText(left, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1String(text.mid(0, 1))); for (int i = 1; i < parm[ 2 ]; ++i) { left += parm[ idxOffset + i - 1 ]; mPainter.drawText(left, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1String(text.mid(i, 1))); } } else { mPainter.drawText(x, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1String(text)); } mPainter.restore(); } //----------------------------------------------------------------------------- // Bitmap //----------------------------------------------------------------------------- void QWinMetaFile::dibBitBlt(long num, short *parm) { if (num > 9) { // DIB image QImage bmpSrc; if (dibToBmp(bmpSrc, (char *)&parm[ 8 ], (num - 8) * 2)) { long raster = toDWord(parm); mPainter.setCompositionMode(winToQtComposition(raster)); // wmf file allow negative width or height mPainter.save(); if (parm[ 5 ] < 0) { // width < 0 => horizontal flip - QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); - mPainter.setMatrix(m, true); + QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); + mPainter.setWorldTransform(m, true); } if (parm[ 4 ] < 0) { // height < 0 => vertical flip - QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); - mPainter.setMatrix(m, true); + QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); + mPainter.setWorldTransform(m, true); } mPainter.drawImage(parm[ 7 ], parm[ 6 ], bmpSrc, parm[ 3 ], parm[ 2 ], parm[ 5 ], parm[ 4 ]); mPainter.restore(); } } else { qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::dibBitBlt without image: not implemented"; } } //----------------------------------------------------------------------------- void QWinMetaFile::dibStretchBlt(long num, short *parm) { QImage bmpSrc; if (dibToBmp(bmpSrc, (char *)&parm[ 10 ], (num - 10) * 2)) { long raster = toDWord(parm); mPainter.setCompositionMode(winToQtComposition(raster)); // wmf file allow negative width or height mPainter.save(); if (parm[ 7 ] < 0) { // width < 0 => horizontal flip - QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); - mPainter.setMatrix(m, true); + QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); + mPainter.setWorldTransform(m, true); } if (parm[ 6 ] < 0) { // height < 0 => vertical flip - QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); - mPainter.setMatrix(m, true); + QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); + mPainter.setWorldTransform(m, true); } bmpSrc = bmpSrc.copy(parm[ 5 ], parm[ 4 ], parm[ 3 ], parm[ 2 ]); // TODO: scale the bitmap ( QImage::scale(parm[ 7 ], parm[ 6 ]) is actually too slow ) mPainter.drawImage(parm[ 9 ], parm[ 8 ], bmpSrc); mPainter.restore(); } } //----------------------------------------------------------------------------- void QWinMetaFile::stretchDib(long num, short *parm) { QImage bmpSrc; if (dibToBmp(bmpSrc, (char *)&parm[ 11 ], (num - 11) * 2)) { long raster = toDWord(parm); mPainter.setCompositionMode(winToQtComposition(raster)); // wmf file allow negative width or height mPainter.save(); if (parm[ 8 ] < 0) { // width < 0 => horizontal flip - QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); - mPainter.setMatrix(m, true); + QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F); + mPainter.setWorldTransform(m, true); } if (parm[ 7 ] < 0) { // height < 0 => vertical flip - QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); - mPainter.setMatrix(m, true); + QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F); + mPainter.setWorldTransform(m, true); } bmpSrc = bmpSrc.copy(parm[ 6 ], parm[ 5 ], parm[ 4 ], parm[ 3 ]); // TODO: scale the bitmap ( QImage::scale(parm[ 8 ], parm[ 7 ]) is actually too slow ) mPainter.drawImage(parm[ 10 ], parm[ 9 ], bmpSrc); mPainter.restore(); } } //----------------------------------------------------------------------------- void QWinMetaFile::dibCreatePatternBrush(long num, short *parm) { WinObjPatternBrushHandle *handle = new WinObjPatternBrushHandle; addHandle(handle); QImage bmpSrc; if (dibToBmp(bmpSrc, (char *)&parm[ 2 ], (num - 2) * 2)) { handle->image = bmpSrc; handle->brush.setTextureImage(handle->image); } } //----------------------------------------------------------------------------- // Object handle //----------------------------------------------------------------------------- void QWinMetaFile::selectObject(long, short *parm) { int idx = parm[ 0 ]; if (idx >= 0 && idx < MAX_OBJHANDLE && mObjHandleTab[ idx ]) { mObjHandleTab[ idx ]->apply(mPainter); } } //----------------------------------------------------------------------------- void QWinMetaFile::deleteObject(long, short *parm) { deleteHandle(parm[ 0 ]); } //----------------------------------------------------------------------------- void QWinMetaFile::createEmptyObject(long, short *) { // allocation of an empty object (to keep object counting in sync) WinObjPenHandle *handle = new WinObjPenHandle; addHandle(handle); qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile: unimplemented createObject"; } //----------------------------------------------------------------------------- void QWinMetaFile::createBrushIndirect(long, short *parm) { static Qt::BrushStyle hatchedStyleTab[] = { Qt::HorPattern, Qt::FDiagPattern, Qt::BDiagPattern, Qt::CrossPattern, Qt::DiagCrossPattern }; static Qt::BrushStyle styleTab[] = { Qt::SolidPattern, Qt::NoBrush, Qt::FDiagPattern, /* hatched */ Qt::Dense4Pattern, /* should be custom bitmap pattern */ Qt::HorPattern, /* should be BS_INDEXED (?) */ Qt::VerPattern, /* should be device-independent bitmap */ Qt::Dense6Pattern, /* should be device-independent packed-bitmap */ Qt::Dense2Pattern, /* should be BS_PATTERN8x8 */ Qt::Dense3Pattern /* should be device-independent BS_DIBPATTERN8x8 */ }; Qt::BrushStyle style; short arg; WinObjBrushHandle *handle = new WinObjBrushHandle; addHandle(handle); arg = parm[ 0 ]; if (arg == 2) { arg = parm[ 3 ]; if (arg >= 0 && arg < 5) { style = hatchedStyleTab[ arg ]; } else { qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::createBrushIndirect: invalid hatched brush" << arg; style = Qt::SolidPattern; } } else if (arg >= 0 && arg < 9) { style = styleTab[ arg ]; } else { qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::createBrushIndirect: invalid brush" << arg; style = Qt::SolidPattern; } handle->brush.setStyle(style); handle->brush.setColor(color(parm + 1)); } //----------------------------------------------------------------------------- void QWinMetaFile::createPenIndirect(long, short *parm) { static Qt::PenStyle styleTab[] = { Qt::SolidLine, Qt::DashLine, Qt::DotLine, Qt::DashDotLine, Qt::DashDotDotLine, Qt::NoPen, Qt::SolidLine }; Qt::PenStyle style; WinObjPenHandle *handle = new WinObjPenHandle; addHandle(handle); if (parm[ 0 ] >= 0 && parm[ 0 ] < 6) { style = styleTab[ parm[ 0 ] ]; } else { qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::createPenIndirect: invalid pen" << parm[ 0 ]; style = Qt::SolidLine; } handle->pen.setStyle(style); handle->pen.setColor(color(parm + 3)); handle->pen.setCapStyle(Qt::RoundCap); //int width = 0; // TODO : width of pen proportional to device context width // DOESN'T WORK /* QRect devRec; devRec = mPainter.transformed( mBBox ); width = ( parm[ 0 ] * devRec.width() ) / mBBox.width() ; qCDebug(KTNEFAPPS_LOG) <<"CreatePenIndirect:"; qCDebug(KTNEFAPPS_LOG) <<" log coord. :" << mBBox.width() <<"" << mBBox.height(); qCDebug(KTNEFAPPS_LOG) <<" log. pen :" << parm[ 1 ] <<"" << parm[ 2 ]; qCDebug(KTNEFAPPS_LOG) <<" dev. pen :" << width; handle->pen.setWidth( width ); */ } //----------------------------------------------------------------------------- void QWinMetaFile::createFontIndirect(long, short *parm) { WinObjFontHandle *handle = new WinObjFontHandle; addHandle(handle); QString family(QLatin1String((const char *)&parm[ 9 ])); mRotation = -parm[ 2 ] / 10; // text rotation (in 1/10 degree) // TODO: memorisation of rotation in object Font handle->font.setFamily(family); handle->font.setFixedPitch(((parm[ 8 ] & 0x01) == 0)); // TODO: investigation why some test case need -2. (size of font in logical point) handle->font.setPointSize(qAbs(parm[ 0 ]) - 2); handle->font.setWeight((parm[ 4 ] >> 3)); handle->font.setItalic((parm[ 5 ] & 0x01)); handle->font.setUnderline((parm[ 5 ] & 0x100)); } //----------------------------------------------------------------------------- // Misc //----------------------------------------------------------------------------- void QWinMetaFile::noop(long, short *) { } void QWinMetaFile::end(long, short *) { // end of file : // qCDebug(KTNEFAPPS_LOG) <<"END bbox=(" << mBBox.left() <<";" << mBBox.top() <<";" << mBBox.width() <<";" << mBBox.height() <<")"; } //----------------------------------------------------------------------------- unsigned short QWinMetaFile::calcCheckSum(WmfPlaceableHeader *apmfh) { WORD *lpWord; WORD wResult, i; // Start with the first word wResult = *(lpWord = (WORD *)(apmfh)); // XOR in each of the other 9 words for (i = 1; i <= 9; ++i) { wResult ^= lpWord[ i ]; } return wResult; } //----------------------------------------------------------------------------- int QWinMetaFile::findFunc(unsigned short aFunc) const { int i; for (i = 0; metaFuncTab[ i ].name; ++i) { if (metaFuncTab[ i ].func == aFunc) { return i; } } // here : unknown function return i; } //----------------------------------------------------------------------------- QPolygon *QWinMetaFile::pointArray(short num, short *parm) { int i; mPoints.resize(num); for (i = 0; i < num; ++i, parm += 2) { mPoints.setPoint(i, parm[ 0 ], parm[ 1 ]); } return &mPoints; } //----------------------------------------------------------------------------- unsigned int QWinMetaFile::toDWord(short *parm) { unsigned int l; #if !defined(WORDS_BIGENDIAN) l = *(unsigned int *)(parm); #else char *bytes; char swap[ 4 ]; bytes = (char *)parm; swap[ 0 ] = bytes[ 2 ]; swap[ 1 ] = bytes[ 3 ]; swap[ 2 ] = bytes[ 0 ]; swap[ 3 ] = bytes[ 1 ]; l = *(unsigned int *)(swap); #endif return l; } //----------------------------------------------------------------------------- QColor QWinMetaFile::color(short *parm) { unsigned int colorRef; int red, green, blue; colorRef = toDWord(parm) & 0xffffff; red = colorRef & 255; green = (colorRef >> 8) & 255; blue = (colorRef >> 16) & 255; return QColor(red, green, blue); } //----------------------------------------------------------------------------- void QWinMetaFile::xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int &angleStart, int &angleLength) { float aStart, aLength; aStart = atan2((double)yStart, (double)xStart); aLength = atan2((double)yEnd, (double)xEnd) - aStart; angleStart = (int)(aStart * 2880 / 3.14166); angleLength = (int)(aLength * 2880 / 3.14166); if (angleLength < 0) { angleLength = 5760 + angleLength; } } //----------------------------------------------------------------------------- void QWinMetaFile::addHandle(WinObjHandle *handle) { int idx; for (idx = 0; idx < MAX_OBJHANDLE; idx++) { if (mObjHandleTab[ idx ] == nullptr) { break; } } if (idx < MAX_OBJHANDLE) { mObjHandleTab[ idx ] = handle; } else { qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile error: handle table full !"; } } //----------------------------------------------------------------------------- void QWinMetaFile::deleteHandle(int idx) { if (idx >= 0 && idx < MAX_OBJHANDLE && mObjHandleTab[ idx ]) { delete mObjHandleTab[ idx ]; mObjHandleTab[ idx ] = nullptr; } } //----------------------------------------------------------------------------- QPainter::CompositionMode QWinMetaFile::winToQtComposition(short parm) const { static const QPainter::CompositionMode opTab[] = { // ### untested (conversion from Qt::RasterOp) QPainter::CompositionMode_Source, // Qt::CopyROP QPainter::CompositionMode_Clear, // Qt::ClearROP QPainter::CompositionMode_SourceOut, // Qt::NandROP QPainter::CompositionMode_SourceOut, // Qt::NotAndROP QPainter::CompositionMode_DestinationOut, // Qt::NotCopyROP QPainter::CompositionMode_DestinationOut, // Qt::AndNotROP QPainter::CompositionMode_DestinationOut, // Qt::NotROP QPainter::CompositionMode_Xor, // Qt::XorROP QPainter::CompositionMode_Source, // Qt::NorROP QPainter::CompositionMode_SourceIn, // Qt::AndROP QPainter::CompositionMode_SourceIn, // Qt::NotXorROP QPainter::CompositionMode_Destination, // Qt::NopROP QPainter::CompositionMode_Destination, // Qt::NotOrROP QPainter::CompositionMode_Source, // Qt::CopyROP QPainter::CompositionMode_Source, // Qt::OrNotROP QPainter::CompositionMode_SourceOver, // Qt::OrROP QPainter::CompositionMode_Source // Qt::SetROP }; if (parm > 0 && parm <= 16) { return opTab[ parm ]; } else { return QPainter::CompositionMode_Source; } } //----------------------------------------------------------------------------- QPainter::CompositionMode QWinMetaFile::winToQtComposition(long parm) const { /* TODO: Ternary raster operations 0x00C000CA dest = (source AND pattern) 0x00F00021 dest = pattern 0x00FB0A09 dest = DPSnoo 0x005A0049 dest = pattern XOR dest */ static const struct OpTab { long winRasterOp; QPainter::CompositionMode qtRasterOp; } opTab[] = { // ### untested (conversion from Qt::RasterOp) { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP { 0x00EE0086, QPainter::CompositionMode_SourceOver }, // OrROP { 0x008800C6, QPainter::CompositionMode_SourceIn }, // AndROP { 0x00660046, QPainter::CompositionMode_Xor }, // XorROP { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP }; int i; for (i = 0; i < 15; ++i) { if (opTab[ i ].winRasterOp == parm) { break; } } if (i < 15) { return opTab[ i ].qtRasterOp; } else { return QPainter::CompositionMode_Source; } } //----------------------------------------------------------------------------- bool QWinMetaFile::dibToBmp(QImage &bmp, const char *dib, long size) { typedef struct _BMPFILEHEADER { WORD bmType; DWORD bmSize; WORD bmReserved1; WORD bmReserved2; DWORD bmOffBits; } BMPFILEHEADER; int sizeBmp = size + 14; QByteArray pattern; // BMP header and DIB data pattern.fill(0, sizeBmp); //resize and fill pattern.insert(14, QByteArray::fromRawData(dib, size)); // add BMP header BMPFILEHEADER *bmpHeader; bmpHeader = (BMPFILEHEADER *)(pattern.constData()); bmpHeader->bmType = 0x4D42; bmpHeader->bmSize = sizeBmp; if (!bmp.loadFromData((const uchar *)bmpHeader, pattern.size(), "BMP")) { qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::dibToBmp: invalid bitmap"; return false; } else { // if ( bmp.save("/home/software/kde-cvs/qt/examples/wmf/test.bmp", "BMP") ) // if ( bmp.load( "/home/software/kde-cvs/qt/examples/wmf/test.bmp", "BMP" ) ) // fprintf(stderr, "Bitmap ok \n"); return true; } } diff --git a/src/collectionpage/collectiontemplatespage.cpp b/src/collectionpage/collectiontemplatespage.cpp index 7800d7221..0c333c9e2 100644 --- a/src/collectionpage/collectiontemplatespage.cpp +++ b/src/collectionpage/collectiontemplatespage.cpp @@ -1,130 +1,130 @@ /* Copyright (C) 2009-2019 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 "collectiontemplatespage.h" #include "mailcommon/mailkernel.h" #include #include "TemplateParser/TemplatesConfiguration" #include "templatesconfiguration_kfg.h" #include #include #include #include using namespace Akonadi; using namespace MailCommon; CollectionTemplatesPage::CollectionTemplatesPage(QWidget *parent) : CollectionPropertiesPage(parent) { setObjectName(QStringLiteral("KMail::CollectionTemplatesPage")); setPageTitle(i18n("Templates")); init(); } CollectionTemplatesPage::~CollectionTemplatesPage() { } bool CollectionTemplatesPage::canHandle(const Collection &collection) const { return !CommonKernel->isSystemFolderCollection(collection) || CommonKernel->isMainFolderCollection(collection); } void CollectionTemplatesPage::init() { QVBoxLayout *topLayout = new QVBoxLayout(this); QHBoxLayout *topItems = new QHBoxLayout; - topItems->setMargin(0); + topItems->setContentsMargins(0, 0, 0, 0); topLayout->addLayout(topItems); mCustom = new QCheckBox(i18n("&Use custom message templates in this folder"), this); connect(mCustom, &QCheckBox::clicked, this, &CollectionTemplatesPage::slotChanged); topItems->addWidget(mCustom, Qt::AlignLeft); mWidget = new TemplateParser::TemplatesConfiguration(this, QStringLiteral("folder-templates")); connect(mWidget, &TemplateParser::TemplatesConfiguration::changed, this, &CollectionTemplatesPage::slotChanged); 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. topItems->addStretch(9); topItems->addWidget(mWidget->helpLabel(), Qt::AlignRight); topLayout->addWidget(mWidget); QHBoxLayout *btns = new QHBoxLayout(); QPushButton *copyGlobal = new QPushButton(i18n("&Copy Global Templates"), this); copyGlobal->setEnabled(false); btns->addWidget(copyGlobal); topLayout->addLayout(btns); connect(mCustom, &QCheckBox::toggled, mWidget, &TemplateParser::TemplatesConfiguration::setEnabled); connect(mCustom, &QCheckBox::toggled, copyGlobal, &QPushButton::setEnabled); connect(copyGlobal, &QPushButton::clicked, this, &CollectionTemplatesPage::slotCopyGlobal); } void CollectionTemplatesPage::load(const Collection &col) { const QSharedPointer fd = FolderSettings::forCollection(col, false); if (!fd) { return; } mCollectionId = QString::number(col.id()); TemplateParser::Templates t(mCollectionId); mCustom->setChecked(t.useCustomTemplates()); mIdentity = fd->identity(); mWidget->loadFromFolder(mCollectionId, mIdentity); mChanged = false; } void CollectionTemplatesPage::save(Collection &) { if (mChanged && !mCollectionId.isEmpty()) { TemplateParser::Templates t(mCollectionId); //qCDebug(KMAIL_LOG) << "use custom templates for folder" << fid <<":" << mCustom->isChecked(); t.setUseCustomTemplates(mCustom->isChecked()); t.save(); mWidget->saveToFolder(mCollectionId); } } void CollectionTemplatesPage::slotCopyGlobal() { if (mIdentity) { mWidget->loadFromIdentity(mIdentity); } else { mWidget->loadFromGlobal(); } } void CollectionTemplatesPage::slotChanged() { mChanged = true; } diff --git a/src/configuredialog/configagentdelegate.cpp b/src/configuredialog/configagentdelegate.cpp index e481e4b73..55d06770c 100644 --- a/src/configuredialog/configagentdelegate.cpp +++ b/src/configuredialog/configagentdelegate.cpp @@ -1,247 +1,247 @@ /* Copyright (c) 2010 Casey Link Copyright (c) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company Copyright (c) 2006-2008 Tobias Koenig 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 "configagentdelegate.h" #include #include #include #include #include #include #include #include #include #include using Akonadi::AgentInstanceModel; using Akonadi::AgentInstance; static const int s_delegatePaddingSize = 7; struct Icons { Icons() : readyPixmap(QIcon::fromTheme(QStringLiteral("user-online")).pixmap(QSize(16, 16))) , syncPixmap(QIcon::fromTheme(QStringLiteral("network-connect")).pixmap(QSize(16, 16))) , errorPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(QSize(16, 16))) , offlinePixmap(QIcon::fromTheme(QStringLiteral("network-disconnect")).pixmap(QSize(16, 16))) , checkMailIcon(QIcon::fromTheme(QStringLiteral("mail-receive"))) { } QPixmap readyPixmap, syncPixmap, errorPixmap, offlinePixmap; QIcon checkMailIcon; }; Q_GLOBAL_STATIC(Icons, s_icons) ConfigAgentDelegate::ConfigAgentDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QTextDocument *ConfigAgentDelegate::document(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return nullptr; } const QString name = index.model()->data(index, Qt::DisplayRole).toString(); int status = index.model()->data(index, AgentInstanceModel::StatusRole).toInt(); uint progress = index.model()->data(index, AgentInstanceModel::ProgressRole).toUInt(); const QString statusMessage = index.model()->data(index, AgentInstanceModel::StatusMessageRole).toString(); QTextDocument *document = new QTextDocument(nullptr); const QSize decorationSize(KIconLoader::global()->currentSize(KIconLoader::Desktop), KIconLoader::global()->currentSize(KIconLoader::Desktop)); const QVariant data = index.model()->data(index, Qt::DecorationRole); if (data.isValid() && data.type() == QVariant::Icon) { document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("agent_icon")), qvariant_cast(data).pixmap(decorationSize)); } if (!index.data(AgentInstanceModel::OnlineRole).toBool()) { document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("status_icon")), s_icons->offlinePixmap); } else if (status == AgentInstance::Idle) { document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("status_icon")), s_icons->readyPixmap); } else if (status == AgentInstance::Running) { document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("status_icon")), s_icons->syncPixmap); } else { document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("status_icon")), s_icons->errorPixmap); } QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } QColor textColor; if (option.state & QStyle::State_Selected) { textColor = option.palette.color(cg, QPalette::HighlightedText); } else { textColor = option.palette.color(cg, QPalette::Text); } const QString content = QStringLiteral( "" "" "" "" "" "" "").arg(textColor.name().toUpper(), name) + QStringLiteral( "" "" "").arg(statusMessage).arg(status == 1 ? QStringLiteral("(%1%)").arg(progress) : QLatin1String("")) + QLatin1String("
  %2
%1 %2
"); document->setHtml(content); return document; } void ConfigAgentDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } QTextDocument *doc = document(option, index); if (!doc) { return; } QStyleOptionButton buttonOpt = buttonOption(option); doc->setTextWidth(option.rect.width() - buttonOpt.rect.width()); painter->setRenderHint(QPainter::Antialiasing); QPen pen = painter->pen(); QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter); painter->save(); painter->translate(option.rect.topLeft()); doc->drawContents(painter); QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOpt, painter); painter->restore(); painter->setPen(pen); drawFocus(painter, option, option.rect); delete doc; } QSize ConfigAgentDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const { const int iconHeight = KIconLoader::global()->currentSize(KIconLoader::Desktop) + (s_delegatePaddingSize * 2); //icon height + padding either side const int textHeight = option.fontMetrics.height() + qMax(option.fontMetrics.height(), 16) + (s_delegatePaddingSize * 2); //height of text + icon/text + padding either side return QSize(1, qMax(iconHeight, textHeight)); //any width,the view will give us the whole thing in list mode } QWidget *ConfigAgentDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const { return nullptr; } bool ConfigAgentDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { Q_UNUSED(model); if (!index.isValid()) { return false; } if (!((event->type() == QEvent::MouseButtonRelease) || (event->type() == QEvent::MouseButtonPress) || (event->type() == QEvent::MouseMove))) { return false; } QMouseEvent *me = static_cast(event); const QPoint mousePos = me->pos() - option.rect.topLeft(); QStyleOptionButton buttonOpt = buttonOption(option); if (buttonOpt.rect.contains(mousePos)) { switch (event->type()) { case QEvent::MouseButtonPress: return false; case QEvent::MouseButtonRelease: { QPoint pos = buttonOpt.rect.bottomLeft() + option.rect.topLeft(); const QString ident = index.data(Akonadi::AgentInstanceModel::InstanceIdentifierRole).toString(); Q_EMIT optionsClicked(ident, pos); return true; } default: return false; } } return false; } void ConfigAgentDelegate::drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { if (option.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = rect; o.state |= QStyle::State_KeyboardFocusChange; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) - ? QPalette::Highlight : QPalette::Background); + ? QPalette::Highlight : QPalette::Window); QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); } } QStyleOptionButton ConfigAgentDelegate::buttonOption(const QStyleOptionViewItem &option) const { const QString label = i18n("Retrieval Options"); QStyleOptionButton buttonOpt; QRect buttonRect = option.rect; int height = option.rect.height() / 2; int width = 22 + option.fontMetrics.boundingRect(label).width() + 40; // icon size + label size + arrow and padding buttonRect.setTop(0); buttonRect.setHeight(height); buttonRect.setLeft(option.rect.right() - width); buttonRect.setWidth(width); buttonOpt.rect = buttonRect; buttonOpt.state = option.state; buttonOpt.text = label; buttonOpt.palette = option.palette; buttonOpt.features = QStyleOptionButton::HasMenu; buttonOpt.icon = s_icons->checkMailIcon; buttonOpt.iconSize = QSize(22, 22); // FIXME don't hardcode this icon size return buttonOpt; } diff --git a/src/configuredialog/configureaccountpage.cpp b/src/configuredialog/configureaccountpage.cpp index 50d4e54df..44fc4f9eb 100644 --- a/src/configuredialog/configureaccountpage.cpp +++ b/src/configuredialog/configureaccountpage.cpp @@ -1,418 +1,418 @@ /* Copyright (c) 2013-2019 Montel Laurent This program 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. 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 "configureaccountpage.h" #include "dialog/kmknotify.h" #include "newmailnotifierinterface.h" #include "kmkernel.h" #include "settings/kmailsettings.h" #include "configagentdelegate.h" #include "MessageComposer/MessageComposerSettings" #include "MailCommon/AccountConfigOrderDialog" #include "PimCommon/ConfigureImmutableWidgetUtils" using namespace PimCommon::ConfigureImmutableWidgetUtils; #include using MailTransport::TransportManagementWidget; #include "ui_accountspagereceivingtab.h" #include "MailCommon/MailUtil" #include #include #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include QString AccountsPage::helpAnchor() const { return QStringLiteral("configure-accounts"); } AccountsPage::AccountsPage(QWidget *parent) : ConfigModuleWithTabs(parent) { //Identity Tab: KMail::IdentityPage *identityTab = new KMail::IdentityPage(); addTab(identityTab, i18nc("@title:tab Tab page where the user configures identities", "Identities")); // // "Receiving" tab: // ReceivingTab *receivingTab = new ReceivingTab(); addTab(receivingTab, i18nc("@title:tab Tab page where the user configures accounts to receive mail", "Receiving")); connect(receivingTab, &ReceivingTab::accountListChanged, this, &AccountsPage::accountListChanged); // // "Sending" tab: // SendingTab *sendingTab = new SendingTab(); addTab(sendingTab, i18nc("@title:tab Tab page where the user configures accounts to send mail", "Sending")); // // "Sending" tab: // LdapCompetionTab *ldapCompletionTab = new LdapCompetionTab(); addTab(ldapCompletionTab, i18nc("@title:tab Tab page where the user configures ldap server", "LDAP server")); } AccountsPageSendingTab::~AccountsPageSendingTab() { } QString AccountsPage::SendingTab::helpAnchor() const { return QStringLiteral("configure-accounts-sending"); } AccountsPageSendingTab::AccountsPageSendingTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // label: zero stretch ### FIXME more vlay->addWidget(new QLabel(i18n("Outgoing accounts (add at least one):"), this)); TransportManagementWidget *tmw = new TransportManagementWidget(this); tmw->layout()->setContentsMargins(0, 0, 0, 0); vlay->addWidget(tmw); // "Common options" groupbox: QGroupBox *group = new QGroupBox(i18n("Common Options"), this); vlay->addWidget(group); // a grid layout for the contents of the "common options" group box QGridLayout *glay = new QGridLayout(); group->setLayout(glay); glay->setColumnStretch(2, 10); // "confirm before send" check box: mConfirmSendCheck = new QCheckBox(i18n("Confirm &before send"), group); glay->addWidget(mConfirmSendCheck, 0, 0, 1, 2); connect(mConfirmSendCheck, &QCheckBox::stateChanged, this, &AccountsPageSendingTab::slotEmitChanged); mCheckSpellingBeforeSending = new QCheckBox(i18n("Check spelling before sending"), group); glay->addWidget(mCheckSpellingBeforeSending, 1, 0, 1, 2); connect(mCheckSpellingBeforeSending, &QCheckBox::stateChanged, this, &AccountsPageSendingTab::slotEmitChanged); // "send on check" combo: mSendOnCheckCombo = new KComboBox(group); mSendOnCheckCombo->setEditable(false); mSendOnCheckCombo->addItems(QStringList() << i18n("Never Automatically") << i18n("On Manual Mail Checks") << i18n("On All Mail Checks")); glay->addWidget(mSendOnCheckCombo, 2, 1); connect(mSendOnCheckCombo, QOverload::of(&KComboBox::activated), this, &AccountsPageSendingTab::slotEmitChanged); // "default send method" combo: mSendMethodCombo = new KComboBox(group); mSendMethodCombo->setEditable(false); mSendMethodCombo->addItems(QStringList() << i18n("Send Now") << i18n("Send Later")); glay->addWidget(mSendMethodCombo, 3, 1); connect(mSendMethodCombo, QOverload::of(&KComboBox::activated), this, &AccountsPageSendingTab::slotEmitChanged); // labels: QLabel *l = new QLabel(i18n("Send &messages in outbox folder:"), group); l->setBuddy(mSendOnCheckCombo); glay->addWidget(l, 2, 0); QString msg = i18n(KMailSettings::self()->sendOnCheckItem()->whatsThis().toUtf8().constData()); l->setWhatsThis(msg); mSendOnCheckCombo->setWhatsThis(msg); l = new QLabel(i18n("Defa&ult send method:"), group); l->setBuddy(mSendMethodCombo); glay->addWidget(l, 3, 0); } void AccountsPage::SendingTab::doLoadFromGlobalSettings() { mSendOnCheckCombo->setCurrentIndex(KMailSettings::self()->sendOnCheck()); } void AccountsPage::SendingTab::doLoadOther() { mSendMethodCombo->setCurrentIndex(MessageComposer::MessageComposerSettings::self()->sendImmediate() ? 0 : 1); loadWidget(mConfirmSendCheck, KMailSettings::self()->confirmBeforeSendItem()); loadWidget(mCheckSpellingBeforeSending, KMailSettings::self()->checkSpellingBeforeSendItem()); } void AccountsPage::SendingTab::save() { KMailSettings::self()->setSendOnCheck(mSendOnCheckCombo->currentIndex()); saveCheckBox(mConfirmSendCheck, KMailSettings::self()->confirmBeforeSendItem()); saveCheckBox(mCheckSpellingBeforeSending, KMailSettings::self()->checkSpellingBeforeSendItem()); MessageComposer::MessageComposerSettings::self()->setSendImmediate(mSendMethodCombo->currentIndex() == 0); } QString AccountsPage::ReceivingTab::helpAnchor() const { return QStringLiteral("configure-accounts-receiving"); } AccountsPageReceivingTab::AccountsPageReceivingTab(QWidget *parent) : ConfigModuleTab(parent) { const auto service = Akonadi::ServerManager::self()->agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_newmailnotifier_agent")); mNewMailNotifierInterface = new OrgFreedesktopAkonadiNewMailNotifierInterface(service, QStringLiteral("/NewMailNotifierAgent"), QDBusConnection::sessionBus(), this); if (!mNewMailNotifierInterface->isValid()) { qCDebug(KMAIL_LOG) << " org.freedesktop.Akonadi.NewMailNotifierAgent not found. Please verify your installation"; delete mNewMailNotifierInterface; mNewMailNotifierInterface = nullptr; } mAccountsReceiving.setupUi(this); mAccountsReceiving.mAccountsReceiving->setMimeTypeFilter(QStringList() << KMime::Message::mimeType()); mAccountsReceiving.mAccountsReceiving->setCapabilityFilter(QStringList() << QStringLiteral("Resource")); mAccountsReceiving.mAccountsReceiving->setExcludeCapabilities(QStringList() << QStringLiteral("MailTransport") << QStringLiteral("Notes") << QStringLiteral("Autostart")); KConfig specialMailCollection(QStringLiteral("specialmailcollectionsrc")); if (specialMailCollection.hasGroup(QStringLiteral("SpecialCollections"))) { KConfigGroup grp = specialMailCollection.group(QStringLiteral("SpecialCollections")); mAccountsReceiving.mAccountsReceiving->setSpecialCollectionIdentifier(grp.readEntry(QStringLiteral("DefaultResourceId"))); } ConfigAgentDelegate *configDelegate = new ConfigAgentDelegate(mAccountsReceiving.mAccountsReceiving->view()); mAccountsReceiving.mAccountsReceiving->setItemDelegate(configDelegate); connect(configDelegate, &ConfigAgentDelegate::optionsClicked, this, &AccountsPageReceivingTab::slotShowMailCheckMenu); connect(mAccountsReceiving.mVerboseNotificationCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mAccountsReceiving.mOtherNewMailActionsButton, &QAbstractButton::clicked, this, &AccountsPageReceivingTab::slotEditNotifications); connect(mAccountsReceiving.customizeAccountOrder, &QAbstractButton::clicked, this, &AccountsPageReceivingTab::slotCustomizeAccountOrder); mAccountsReceiving.mAccountsReceiving->disconnectAddAccountButton(); QMenu *accountMenu = new QMenu(this); accountMenu->addAction(i18n("Add Mail Account..."), this, &AccountsPageReceivingTab::slotAddMailAccount); accountMenu->addAction(i18n("Custom Account..."), this, &AccountsPageReceivingTab::slotAddCustomAccount); mAccountsReceiving.mAccountsReceiving->addAccountButton()->setMenu(accountMenu); } AccountsPageReceivingTab::~AccountsPageReceivingTab() { delete mNewMailNotifierInterface; mRetrievalHash.clear(); } void AccountsPageReceivingTab::slotAddCustomAccount() { mAccountsReceiving.mAccountsReceiving->slotAddAccount(); } void AccountsPageReceivingTab::slotAddMailAccount() { const QStringList lst = {QStringLiteral("--type"), QStringLiteral("message/rfc822") }; const QString path = QStandardPaths::findExecutable(QStringLiteral("accountwizard")); if (!QProcess::startDetached(path, lst)) { KMessageBox::error(this, i18n("Could not start the account wizard. " "Please make sure you have AccountWizard properly installed."), i18n("Unable to start account wizard")); } } void AccountsPageReceivingTab::slotCustomizeAccountOrder() { if (KMKernel::self()) { QPointer dlg = new MailCommon::AccountConfigOrderDialog(KMKernel::self()->mailCommonSettings(), this); dlg->exec(); delete dlg; } } void AccountsPageReceivingTab::slotShowMailCheckMenu(const QString &ident, const QPoint &pos) { QMenu menu(this); bool IncludeInManualChecks; bool OfflineOnShutdown; bool CheckOnStartup; if (!mRetrievalHash.contains(ident)) { const QString resourceGroupPattern(QStringLiteral("Resource %1")); KConfigGroup group; KConfig *conf = nullptr; if (KMKernel::self()) { group = KConfigGroup(KMKernel::self()->config(), resourceGroupPattern.arg(ident)); } else { conf = new KConfig(QStringLiteral("kmail2rc")); group = KConfigGroup(conf, resourceGroupPattern.arg(ident)); } IncludeInManualChecks = group.readEntry("IncludeInManualChecks", true); // Keep sync with kmkernel, don't forget to change there. OfflineOnShutdown = group.readEntry("OfflineOnShutdown", ident.startsWith(QLatin1String("akonadi_pop3_resource")) ? true : false); CheckOnStartup = group.readEntry("CheckOnStartup", false); QSharedPointer opts(new RetrievalOptions(IncludeInManualChecks, OfflineOnShutdown, CheckOnStartup)); mRetrievalHash.insert(ident, opts); delete conf; } else { QSharedPointer opts = mRetrievalHash.value(ident); IncludeInManualChecks = opts->IncludeInManualChecks; OfflineOnShutdown = opts->OfflineOnShutdown; CheckOnStartup = opts->CheckOnStartup; } if (!MailCommon::Util::isVirtualCollection(ident)) { QAction *manualMailCheck = new QAction(i18nc("Label to a checkbox, so is either checked/unchecked", "Include in Manual Mail Check"), &menu); manualMailCheck->setCheckable(true); manualMailCheck->setChecked(IncludeInManualChecks); manualMailCheck->setData(ident); menu.addAction(manualMailCheck); connect(manualMailCheck, &QAction::toggled, this, &AccountsPageReceivingTab::slotIncludeInCheckChanged); } QAction *switchOffline = new QAction(i18nc("Label to a checkbox, so is either checked/unchecked", "Switch offline on KMail Shutdown"), &menu); switchOffline->setCheckable(true); switchOffline->setChecked(OfflineOnShutdown); switchOffline->setData(ident); menu.addAction(switchOffline); connect(switchOffline, &QAction::toggled, this, &AccountsPageReceivingTab::slotOfflineOnShutdownChanged); QAction *checkOnStartup = new QAction(i18n("Check mail on startup"), &menu); checkOnStartup->setCheckable(true); checkOnStartup->setChecked(CheckOnStartup); checkOnStartup->setData(ident); menu.addAction(checkOnStartup); connect(checkOnStartup, &QAction::toggled, this, &AccountsPageReceivingTab::slotCheckOnStatupChanged); menu.exec(mAccountsReceiving.mAccountsReceiving->view()->mapToGlobal(pos)); } void AccountsPageReceivingTab::slotCheckOnStatupChanged(bool checked) { QAction *action = qobject_cast< QAction * >(sender()); const QString ident = action->data().toString(); QSharedPointer opts = mRetrievalHash.value(ident); opts->CheckOnStartup = checked; slotEmitChanged(); } void AccountsPageReceivingTab::slotIncludeInCheckChanged(bool checked) { QAction *action = qobject_cast< QAction * >(sender()); const QString ident = action->data().toString(); QSharedPointer opts = mRetrievalHash.value(ident); opts->IncludeInManualChecks = checked; slotEmitChanged(); } void AccountsPageReceivingTab::slotOfflineOnShutdownChanged(bool checked) { QAction *action = qobject_cast< QAction * >(sender()); QString ident = action->data().toString(); QSharedPointer opts = mRetrievalHash.value(ident); opts->OfflineOnShutdown = checked; slotEmitChanged(); } void AccountsPage::ReceivingTab::slotEditNotifications() { if (mNewMailNotifierInterface) { mNewMailNotifierInterface->asyncCall(QStringLiteral("showConfigureDialog"), (qlonglong)winId()); } else { KMessageBox::error(this, i18n("New Mail Notifier Agent not registered. Please contact your administrator.")); } } void AccountsPage::ReceivingTab::doLoadFromGlobalSettings() { if (mNewMailNotifierInterface) { mAccountsReceiving.mVerboseNotificationCheck->setChecked(mNewMailNotifierInterface->verboseMailNotification()); } } void AccountsPage::ReceivingTab::save() { // Save Mail notification settings if (mNewMailNotifierInterface) { mNewMailNotifierInterface->setVerboseMailNotification(mAccountsReceiving.mVerboseNotificationCheck->isChecked()); } const QString resourceGroupPattern(QStringLiteral("Resource %1")); QHash >::const_iterator it = mRetrievalHash.cbegin(); const QHash >::const_iterator itEnd = mRetrievalHash.cend(); for (; it != itEnd; ++it) { KConfigGroup group; KConfig *conf = nullptr; if (KMKernel::self()) { group = KConfigGroup(KMKernel::self()->config(), resourceGroupPattern.arg(it.key())); } else { conf = new KConfig(QStringLiteral("kmail2rc")); group = KConfigGroup(conf, resourceGroupPattern.arg(it.key())); } QSharedPointer opts = it.value(); group.writeEntry("IncludeInManualChecks", opts->IncludeInManualChecks); group.writeEntry("OfflineOnShutdown", opts->OfflineOnShutdown); group.writeEntry("CheckOnStartup", opts->CheckOnStartup); delete conf; } } LdapCompetionTab::LdapCompetionTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); mLdapConfigureWidget = new KLDAP::LdapConfigureWidget(this); layout->addWidget(mLdapConfigureWidget); connect(mLdapConfigureWidget, &KLDAP::LdapConfigureWidget::changed, this, QOverload::of(&LdapCompetionTab::changed)); } LdapCompetionTab::~LdapCompetionTab() { } QString LdapCompetionTab::helpAnchor() const { return {}; } void LdapCompetionTab::save() { mLdapConfigureWidget->save(); } void LdapCompetionTab::doLoadOther() { mLdapConfigureWidget->load(); } diff --git a/src/configuredialog/configureappearancepage.cpp b/src/configuredialog/configureappearancepage.cpp index 67c3ac44b..8a6d7b7cf 100644 --- a/src/configuredialog/configureappearancepage.cpp +++ b/src/configuredialog/configureappearancepage.cpp @@ -1,1402 +1,1402 @@ /* Copyright (c) 2013-2019 Montel Laurent This program 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. 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 "configureappearancepage.h" #include "PimCommon/ConfigureImmutableWidgetUtils" using namespace PimCommon::ConfigureImmutableWidgetUtils; #include "configuredialog/colorlistbox.h" #include "messagelist/aggregationcombobox.h" #include "messagelist/aggregationconfigbutton.h" #include "messagelist/themecombobox.h" #include "messagelist/themeconfigbutton.h" #include "messagelistsettings.h" #include "MailCommon/TagWidget" #include "MailCommon/Tag" #include "kmkernel.h" #include "util.h" #include "MailCommon/FolderTreeWidget" #include "kmmainwidget.h" #include "mailcommonsettings_base.h" #include "MessageViewer/ConfigureWidget" #include "messageviewer/messageviewersettings.h" #include "messagelist/messagelistutil.h" #include #include "MessageCore/MessageCoreUtil" #include "settings/kmailsettings.h" #include "MailCommon/MailUtil" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include using KMime::DateFormatter; #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; QString AppearancePage::helpAnchor() const { return QStringLiteral("configure-appearance"); } AppearancePage::AppearancePage(QWidget *parent) : ConfigModuleWithTabs(parent) { // // "General" tab: // ReaderTab *readerTab = new ReaderTab(); addTab(readerTab, i18n("General")); addConfig(MessageViewer::MessageViewerSettings::self(), readerTab); // // "Fonts" tab: // FontsTab *fontsTab = new FontsTab(); addTab(fontsTab, i18n("Fonts")); // // "Colors" tab: // ColorsTab *colorsTab = new ColorsTab(); addTab(colorsTab, i18n("Colors")); // // "Layout" tab: // LayoutTab *layoutTab = new LayoutTab(); addTab(layoutTab, i18n("Layout")); // // "Headers" tab: // HeadersTab *headersTab = new HeadersTab(); addTab(headersTab, i18n("Message List")); // // "Message Tag" tab: // MessageTagTab *messageTagTab = new MessageTagTab(); addTab(messageTagTab, i18n("Message Tags")); } QString AppearancePage::FontsTab::helpAnchor() const { return QStringLiteral("configure-appearance-fonts"); } static const struct { const char *configName; const char *displayName; bool enableFamilyAndSize; bool onlyFixed; } fontNames[] = { { "body-font", I18N_NOOP("Message Body"), true, false }, { "MessageListFont", I18N_NOOP("Message List"), true, false }, { "UnreadMessageFont", I18N_NOOP("Message List - Unread Messages"), false, false }, { "ImportantMessageFont", I18N_NOOP("Message List - Important Messages"), false, false }, { "TodoMessageFont", I18N_NOOP("Message List - Action Item Messages"), false, false }, { "fixed-font", I18N_NOOP("Fixed Width Font"), true, true }, { "composer-font", I18N_NOOP("Composer"), true, false }, { "print-font", I18N_NOOP("Printing Output"), true, false }, }; static const int numFontNames = sizeof fontNames / sizeof *fontNames; AppearancePageFontsTab::AppearancePageFontsTab(QWidget *parent) : ConfigModuleTab(parent) , mActiveFontIndex(-1) { assert(numFontNames == sizeof mFont / sizeof *mFont); // "Use custom fonts" checkbox, followed by
QVBoxLayout *vlay = new QVBoxLayout(this); mCustomFontCheck = new QCheckBox(i18n("&Use custom fonts"), this); vlay->addWidget(mCustomFontCheck); vlay->addWidget(new KSeparator(Qt::Horizontal, this)); connect(mCustomFontCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // "font location" combo box and label: QHBoxLayout *hlay = new QHBoxLayout(); // inherites spacing vlay->addLayout(hlay); mFontLocationCombo = new KComboBox(this); mFontLocationCombo->setEditable(false); mFontLocationCombo->setEnabled(false); // !mCustomFontCheck->isChecked() QStringList fontDescriptions; fontDescriptions.reserve(numFontNames); for (int i = 0; i < numFontNames; ++i) { fontDescriptions << i18n(fontNames[i].displayName); } mFontLocationCombo->addItems(fontDescriptions); QLabel *label = new QLabel(i18n("Apply &to:"), this); label->setBuddy(mFontLocationCombo); label->setEnabled(false); // since !mCustomFontCheck->isChecked() hlay->addWidget(label); hlay->addWidget(mFontLocationCombo); hlay->addStretch(10); mFontChooser = new KFontChooser(this, KFontChooser::DisplayFrame, QStringList(), 4); mFontChooser->setEnabled(false); // since !mCustomFontCheck->isChecked() vlay->addWidget(mFontChooser); connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged); // {en,dis}able widgets depending on the state of mCustomFontCheck: connect(mCustomFontCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled); connect(mCustomFontCheck, &QAbstractButton::toggled, mFontLocationCombo, &QWidget::setEnabled); connect(mCustomFontCheck, &QAbstractButton::toggled, mFontChooser, &QWidget::setEnabled); // load the right font settings into mFontChooser: connect(mFontLocationCombo, QOverload::of(&KComboBox::activated), this, &AppearancePage::FontsTab::slotFontSelectorChanged); } void AppearancePage::FontsTab::slotFontSelectorChanged(int index) { qCDebug(KMAIL_LOG) << "slotFontSelectorChanged() called"; if (index < 0 || index >= mFontLocationCombo->count()) { return; // Should never happen, but it is better to check. } // Save current fontselector setting before we install the new: if (mActiveFontIndex == 0) { mFont[0] = mFontChooser->font(); // hardcode the family and size of "message body" dependant fonts: for (int i = 0; i < numFontNames; ++i) { if (!fontNames[i].enableFamilyAndSize) { // ### shall we copy the font and set the save and re-set // {regular,italic,bold,bold italic} property or should we // copy only family and pointSize? mFont[i].setFamily(mFont[0].family()); mFont[i].setPointSize/*Float?*/ (mFont[0].pointSize/*Float?*/ ()); } } } else if (mActiveFontIndex > 0) { mFont[ mActiveFontIndex ] = mFontChooser->font(); } mActiveFontIndex = index; // Disonnect so the "Apply" button is not activated by the change disconnect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged); // Display the new setting: mFontChooser->setFont(mFont[index], fontNames[index].onlyFixed); connect(mFontChooser, &KFontChooser::fontSelected, this, &ConfigModuleTab::slotEmitChanged); // Disable Family and Size list if we have selected a quote font: mFontChooser->enableColumn(KFontChooser::FamilyList | KFontChooser::SizeList, fontNames[ index ].enableFamilyAndSize); } void AppearancePage::FontsTab::doLoadOther() { if (KMKernel::self()) { KConfigGroup fonts(KMKernel::self()->config(), "Fonts"); mFont[0] = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); for (int i = 0; i < numFontNames; ++i) { const QString configName = QLatin1String(fontNames[i].configName); if (configName == QLatin1String("MessageListFont")) { mFont[i] = MessageList::MessageListSettings::self()->messageListFont(); } else if (configName == QLatin1String("UnreadMessageFont")) { mFont[i] = MessageList::MessageListSettings::self()->unreadMessageFont(); } else if (configName == QLatin1String("ImportantMessageFont")) { mFont[i] = MessageList::MessageListSettings::self()->importantMessageFont(); } else if (configName == QLatin1String("TodoMessageFont")) { mFont[i] = MessageList::MessageListSettings::self()->todoMessageFont(); } else { mFont[i] = fonts.readEntry(configName, (fontNames[i].onlyFixed) ? fixedFont : mFont[0]); } } mCustomFontCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultFonts()); mFontLocationCombo->setCurrentIndex(0); slotFontSelectorChanged(0); } else { setEnabled(false); } } void AppearancePage::FontsTab::save() { if (KMKernel::self()) { KConfigGroup fonts(KMKernel::self()->config(), "Fonts"); // read the current font (might have been modified) if (mActiveFontIndex >= 0) { mFont[ mActiveFontIndex ] = mFontChooser->font(); } const bool customFonts = mCustomFontCheck->isChecked(); MessageCore::MessageCoreSettings::self()->setUseDefaultFonts(!customFonts); for (int i = 0; i < numFontNames; ++i) { const QString configName = QLatin1String(fontNames[i].configName); if (customFonts && configName == QLatin1String("MessageListFont")) { MessageList::MessageListSettings::self()->setMessageListFont(mFont[i]); } else if (customFonts && configName == QLatin1String("UnreadMessageFont")) { MessageList::MessageListSettings::self()->setUnreadMessageFont(mFont[i]); } else if (customFonts && configName == QLatin1String("ImportantMessageFont")) { MessageList::MessageListSettings::self()->setImportantMessageFont(mFont[i]); } else if (customFonts && configName == QLatin1String("TodoMessageFont")) { MessageList::MessageListSettings::self()->setTodoMessageFont(mFont[i]); } else { if (customFonts || fonts.hasKey(configName)) { // Don't write font info when we use default fonts, but write // if it's already there: fonts.writeEntry(configName, mFont[i]); } } } } } void AppearancePage::FontsTab::doResetToDefaultsOther() { mCustomFontCheck->setChecked(false); } QString AppearancePage::ColorsTab::helpAnchor() const { return QStringLiteral("configure-appearance-colors"); } static const struct { const char *configName; const char *displayName; } colorNames[] = { // adjust doLoadOther if you change this: { "QuotedText1", I18N_NOOP("Quoted Text - First Level") }, { "QuotedText2", I18N_NOOP("Quoted Text - Second Level") }, { "QuotedText3", I18N_NOOP("Quoted Text - Third Level") }, { "LinkColor", I18N_NOOP("Link") }, { "UnreadMessageColor", I18N_NOOP("Unread Message") }, { "ImportantMessageColor", I18N_NOOP("Important Message") }, { "TodoMessageColor", I18N_NOOP("Action Item Message") }, { "ColorbarBackgroundPlain", I18N_NOOP("HTML Status Bar Background - No HTML Message") }, { "ColorbarForegroundPlain", I18N_NOOP("HTML Status Bar Foreground - No HTML Message") }, { "ColorbarBackgroundHTML", I18N_NOOP("HTML Status Bar Background - HTML Message") }, { "ColorbarForegroundHTML", I18N_NOOP("HTML Status Bar Foreground - HTML Message") } }; static const int numColorNames = sizeof colorNames / sizeof *colorNames; AppearancePageColorsTab::AppearancePageColorsTab(QWidget *parent) : ConfigModuleTab(parent) { // "use custom colors" check box QVBoxLayout *vlay = new QVBoxLayout(this); mCustomColorCheck = new QCheckBox(i18n("&Use custom colors"), this); vlay->addWidget(mCustomColorCheck); connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); mUseInlineStyle = new QCheckBox(i18n("&Do not change color from original HTML mail"), this); vlay->addWidget(mUseInlineStyle); connect(mUseInlineStyle, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // color list box: mColorList = new ColorListBox(this); mColorList->setEnabled(false); // since !mCustomColorCheck->isChecked() for (int i = 0; i < numColorNames; ++i) { mColorList->addColor(i18n(colorNames[i].displayName)); } vlay->addWidget(mColorList, 1); // "recycle colors" check box: mRecycleColorCheck = new QCheckBox(i18n("Recycle colors on deep "ing"), this); mRecycleColorCheck->setEnabled(false); vlay->addWidget(mRecycleColorCheck); connect(mRecycleColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // close to quota threshold QHBoxLayout *hbox = new QHBoxLayout(); vlay->addLayout(hbox); QLabel *l = new QLabel(i18n("Close to quota threshold:"), this); hbox->addWidget(l); mCloseToQuotaThreshold = new QSpinBox(this); mCloseToQuotaThreshold->setRange(0, 100); mCloseToQuotaThreshold->setSingleStep(1); connect(mCloseToQuotaThreshold, QOverload::of(&QSpinBox::valueChanged), this, &ConfigModuleTab::slotEmitChanged); mCloseToQuotaThreshold->setSuffix(i18n("%")); hbox->addWidget(mCloseToQuotaThreshold); hbox->addWidget(new QWidget(this), 2); // {en,dir}able widgets depending on the state of mCustomColorCheck: connect(mCustomColorCheck, &QAbstractButton::toggled, mColorList, &QWidget::setEnabled); connect(mCustomColorCheck, &QAbstractButton::toggled, mRecycleColorCheck, &QWidget::setEnabled); connect(mCustomColorCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mColorList, &ColorListBox::changed, this, &ConfigModuleTab::slotEmitChanged); } void AppearancePage::ColorsTab::doLoadOther() { mCustomColorCheck->setChecked(!MessageCore::MessageCoreSettings::self()->useDefaultColors()); mUseInlineStyle->setChecked(MessageCore::MessageCoreSettings::self()->useRealHtmlMailColor()); mRecycleColorCheck->setChecked(MessageViewer::MessageViewerSettings::self()->recycleQuoteColors()); mCloseToQuotaThreshold->setValue(KMailSettings::self()->closeToQuotaThreshold()); loadColor(true); } void AppearancePage::ColorsTab::loadColor(bool loadFromConfig) { if (KMKernel::self()) { KConfigGroup reader(KMKernel::self()->config(), "Reader"); static const QColor defaultColor[ numColorNames ] = { MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor(), MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor(), MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor(), MessageCore::ColorUtil::self()->linkColor(), // link MessageList::Util::unreadDefaultMessageColor(), // unread mgs MessageList::Util::importantDefaultMessageColor(), // important msg MessageList::Util::todoDefaultMessageColor(), // action item mgs Qt::lightGray, // colorbar plain bg Qt::black, // colorbar plain fg Qt::black, // colorbar html bg Qt::white // colorbar html fg }; for (int i = 0; i < numColorNames; ++i) { if (loadFromConfig) { const QString configName = QLatin1String(colorNames[i].configName); if (configName == QLatin1String("UnreadMessageColor")) { mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->unreadMessageColor()); } else if (configName == QLatin1String("ImportantMessageColor")) { mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->importantMessageColor()); } else if (configName == QLatin1String("TodoMessageColor")) { mColorList->setColorSilently(i, MessageList::MessageListSettings::self()->todoMessageColor()); } else { mColorList->setColorSilently(i, reader.readEntry(configName, defaultColor[i])); } } else { mColorList->setColorSilently(i, defaultColor[i]); } } } else { setEnabled(false); } } void AppearancePage::ColorsTab::doResetToDefaultsOther() { mCustomColorCheck->setChecked(false); mUseInlineStyle->setChecked(false); mRecycleColorCheck->setChecked(false); mCloseToQuotaThreshold->setValue(80); loadColor(false); } void AppearancePage::ColorsTab::save() { if (!KMKernel::self()) { return; } KConfigGroup reader(KMKernel::self()->config(), "Reader"); bool customColors = mCustomColorCheck->isChecked(); MessageCore::MessageCoreSettings::self()->setUseDefaultColors(!customColors); MessageCore::MessageCoreSettings::self()->setUseRealHtmlMailColor(mUseInlineStyle->isChecked()); for (int i = 0; i < numColorNames; ++i) { const QString configName = QLatin1String(colorNames[i].configName); if (customColors && configName == QLatin1String("UnreadMessageColor")) { MessageList::MessageListSettings::self()->setUnreadMessageColor(mColorList->color(i)); } else if (customColors && configName == QLatin1String("ImportantMessageColor")) { MessageList::MessageListSettings::self()->setImportantMessageColor(mColorList->color(i)); } else if (customColors && configName == QLatin1String("TodoMessageColor")) { MessageList::MessageListSettings::self()->setTodoMessageColor(mColorList->color(i)); } else { if (customColors || reader.hasKey(configName)) { reader.writeEntry(configName, mColorList->color(i)); } } } MessageViewer::MessageViewerSettings::self()->setRecycleQuoteColors(mRecycleColorCheck->isChecked()); KMailSettings::self()->setCloseToQuotaThreshold(mCloseToQuotaThreshold->value()); } QString AppearancePage::LayoutTab::helpAnchor() const { return QStringLiteral("configure-appearance-layout"); } AppearancePageLayoutTab::AppearancePageLayoutTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "folder list" radio buttons: populateButtonGroup(mFolderListGroupBox = new QGroupBox(this), mFolderListGroup = new QButtonGroup(this), Qt::Vertical, KMailSettings::self()->folderListItem()); vlay->addWidget(mFolderListGroupBox); connect(mFolderListGroup, QOverload::of(&QButtonGroup::buttonClicked), this, &ConfigModuleTab::slotEmitChanged); QHBoxLayout *folderCBHLayout = new QHBoxLayout; mFolderQuickSearchCB = new QCheckBox(i18n("Show folder quick search field"), this); connect(mFolderQuickSearchCB, &QAbstractButton::toggled, this, &ConfigModuleTab::slotEmitChanged); folderCBHLayout->addWidget(mFolderQuickSearchCB); vlay->addLayout(folderCBHLayout); // "favorite folders view mode" radio buttons: mFavoriteFoldersViewGroupBox = new QGroupBox(this); mFavoriteFoldersViewGroupBox->setTitle(i18n("Show Favorite Folders View")); mFavoriteFoldersViewGroupBox->setLayout(new QVBoxLayout()); mFavoriteFoldersViewGroup = new QButtonGroup(this); connect(mFavoriteFoldersViewGroup, QOverload::of(&QButtonGroup::buttonClicked), this, &ConfigModuleTab::slotEmitChanged); QRadioButton *favoriteFoldersViewHiddenRadio = new QRadioButton(i18n("Never"), mFavoriteFoldersViewGroupBox); mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewHiddenRadio, static_cast(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::HiddenMode)); mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewHiddenRadio); QRadioButton *favoriteFoldersViewIconsRadio = new QRadioButton(i18n("As icons"), mFavoriteFoldersViewGroupBox); mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewIconsRadio, static_cast(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode)); mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewIconsRadio); QRadioButton *favoriteFoldersViewListRadio = new QRadioButton(i18n("As list"), mFavoriteFoldersViewGroupBox); mFavoriteFoldersViewGroup->addButton(favoriteFoldersViewListRadio, static_cast(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode)); mFavoriteFoldersViewGroupBox->layout()->addWidget(favoriteFoldersViewListRadio); vlay->addWidget(mFavoriteFoldersViewGroupBox); // "folder tooltips" radio buttons: mFolderToolTipsGroupBox = new QGroupBox(this); mFolderToolTipsGroupBox->setTitle(i18n("Folder Tooltips")); mFolderToolTipsGroupBox->setLayout(new QVBoxLayout()); mFolderToolTipsGroup = new QButtonGroup(this); connect(mFolderToolTipsGroup, QOverload::of(&QButtonGroup::buttonClicked), this, &ConfigModuleTab::slotEmitChanged); QRadioButton *folderToolTipsAlwaysRadio = new QRadioButton(i18n("Always"), mFolderToolTipsGroupBox); mFolderToolTipsGroup->addButton(folderToolTipsAlwaysRadio, static_cast< int >(FolderTreeWidget::DisplayAlways)); mFolderToolTipsGroupBox->layout()->addWidget(folderToolTipsAlwaysRadio); QRadioButton *folderToolTipsNeverRadio = new QRadioButton(i18n("Never"), mFolderToolTipsGroupBox); mFolderToolTipsGroup->addButton(folderToolTipsNeverRadio, static_cast< int >(FolderTreeWidget::DisplayNever)); mFolderToolTipsGroupBox->layout()->addWidget(folderToolTipsNeverRadio); vlay->addWidget(mFolderToolTipsGroupBox); // "show reader window" radio buttons: populateButtonGroup(mReaderWindowModeGroupBox = new QGroupBox(this), mReaderWindowModeGroup = new QButtonGroup(this), Qt::Vertical, KMailSettings::self()->readerWindowModeItem()); vlay->addWidget(mReaderWindowModeGroupBox); connect(mReaderWindowModeGroup, QOverload::of(&QButtonGroup::buttonClicked), this, &ConfigModuleTab::slotEmitChanged); vlay->addStretch(10); // spacer } void AppearancePage::LayoutTab::doLoadOther() { loadWidget(mFolderListGroupBox, mFolderListGroup, KMailSettings::self()->folderListItem()); loadWidget(mReaderWindowModeGroupBox, mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem()); if (KMKernel::self()) { loadWidget(mFavoriteFoldersViewGroupBox, mFavoriteFoldersViewGroup, KMKernel::self()->mailCommonSettings()->favoriteCollectionViewModeItem()); } loadWidget(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem()); const int checkedFolderToolTipsPolicy = KMailSettings::self()->toolTipDisplayPolicy(); if (checkedFolderToolTipsPolicy >= 0) { mFolderToolTipsGroup->button(checkedFolderToolTipsPolicy)->setChecked(true); } } void AppearancePage::LayoutTab::save() { saveButtonGroup(mFolderListGroup, KMailSettings::self()->folderListItem()); saveButtonGroup(mReaderWindowModeGroup, KMailSettings::self()->readerWindowModeItem()); if (KMKernel::self()) { saveButtonGroup(mFavoriteFoldersViewGroup, KMKernel::self()->mailCommonSettings()->favoriteCollectionViewModeItem()); } saveCheckBox(mFolderQuickSearchCB, KMailSettings::self()->enableFolderQuickSearchItem()); KMailSettings::self()->setToolTipDisplayPolicy(mFolderToolTipsGroup->checkedId()); } // // Appearance Message List // QString AppearancePage::HeadersTab::helpAnchor() const { return QStringLiteral("configure-appearance-headers"); } static const struct { const char *displayName; DateFormatter::FormatType dateDisplay; } dateDisplayConfig[] = { { I18N_NOOP("Sta&ndard format (%1)"), KMime::DateFormatter::CTime }, { I18N_NOOP("Locali&zed format (%1)"), KMime::DateFormatter::Localized }, { I18N_NOOP("Smart for&mat (%1)"), KMime::DateFormatter::Fancy }, { I18N_NOOP("C&ustom format:"), KMime::DateFormatter::Custom } }; static const int numDateDisplayConfig = sizeof dateDisplayConfig / sizeof *dateDisplayConfig; AppearancePageHeadersTab::AppearancePageHeadersTab(QWidget *parent) : ConfigModuleTab(parent) , mCustomDateFormatEdit(nullptr) { QVBoxLayout *vlay = new QVBoxLayout(this); // "General Options" group: QGroupBox *group = new QGroupBox(i18nc("General options for the message list.", "General"), this); QVBoxLayout *gvlay = new QVBoxLayout(group); mDisplayMessageToolTips = new QCheckBox( MessageList::MessageListSettings::self()->messageToolTipEnabledItem()->label(), group); gvlay->addWidget(mDisplayMessageToolTips); connect(mDisplayMessageToolTips, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); // "Aggregation" using MessageList::Utils::AggregationComboBox; mAggregationComboBox = new AggregationComboBox(group); QLabel *aggregationLabel = new QLabel(i18n("Default aggregation:"), group); aggregationLabel->setBuddy(mAggregationComboBox); using MessageList::Utils::AggregationConfigButton; AggregationConfigButton *aggregationConfigButton = new AggregationConfigButton(group, mAggregationComboBox); QHBoxLayout *aggregationLayout = new QHBoxLayout(); aggregationLayout->addWidget(aggregationLabel, 1); aggregationLayout->addWidget(mAggregationComboBox, 1); aggregationLayout->addWidget(aggregationConfigButton, 0); gvlay->addLayout(aggregationLayout); connect(aggregationConfigButton, &MessageList::Utils::AggregationConfigButton::configureDialogCompleted, this, &AppearancePageHeadersTab::slotSelectDefaultAggregation); connect(mAggregationComboBox, QOverload::of(&MessageList::Utils::AggregationComboBox::activated), this, &ConfigModuleTab::slotEmitChanged); // "Theme" using MessageList::Utils::ThemeComboBox; mThemeComboBox = new ThemeComboBox(group); QLabel *themeLabel = new QLabel(i18n("Default theme:"), group); themeLabel->setBuddy(mThemeComboBox); using MessageList::Utils::ThemeConfigButton; ThemeConfigButton *themeConfigButton = new ThemeConfigButton(group, mThemeComboBox); QHBoxLayout *themeLayout = new QHBoxLayout(); themeLayout->addWidget(themeLabel, 1); themeLayout->addWidget(mThemeComboBox, 1); themeLayout->addWidget(themeConfigButton, 0); gvlay->addLayout(themeLayout); connect(themeConfigButton, &MessageList::Utils::ThemeConfigButton::configureDialogCompleted, this, &AppearancePageHeadersTab::slotSelectDefaultTheme); connect(mThemeComboBox, QOverload::of(&MessageList::Utils::ThemeComboBox::activated), this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(group); // "Date Display" group: mDateDisplayBox = new QGroupBox(this); mDateDisplayBox->setTitle(i18n("Date Display")); mDateDisplay = new QButtonGroup(this); mDateDisplay->setExclusive(true); gvlay = new QVBoxLayout(mDateDisplayBox); for (int i = 0; i < numDateDisplayConfig; ++i) { const char *label = dateDisplayConfig[i].displayName; QString buttonLabel; if (QString::fromLatin1(label).contains(QLatin1String("%1"))) { buttonLabel = i18n(label, DateFormatter::formatCurrentDate(dateDisplayConfig[i].dateDisplay)); } else { buttonLabel = i18n(label); } if (dateDisplayConfig[i].dateDisplay == DateFormatter::Custom) { QWidget *hbox = new QWidget(mDateDisplayBox); QHBoxLayout *hboxHBoxLayout = new QHBoxLayout(hbox); - hboxHBoxLayout->setMargin(0); + hboxHBoxLayout->setContentsMargins(0, 0, 0, 0); QRadioButton *radio = new QRadioButton(buttonLabel, hbox); hboxHBoxLayout->addWidget(radio); mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay); mCustomDateFormatEdit = new KLineEdit(hbox); hboxHBoxLayout->addWidget(mCustomDateFormatEdit); mCustomDateFormatEdit->setEnabled(false); hboxHBoxLayout->setStretchFactor(mCustomDateFormatEdit, 1); connect(radio, &QAbstractButton::toggled, mCustomDateFormatEdit, &QWidget::setEnabled); connect(mCustomDateFormatEdit, &QLineEdit::textChanged, this, &ConfigModuleTab::slotEmitChanged); QLabel *formatHelp = new QLabel( i18n("Custom format information..."), hbox); formatHelp->setContextMenuPolicy(Qt::NoContextMenu); connect(formatHelp, &QLabel::linkActivated, this, &AppearancePageHeadersTab::slotLinkClicked); hboxHBoxLayout->addWidget(formatHelp); mCustomDateWhatsThis = i18n("

These expressions may be used for the date:" "

" "
    " "
  • d - the day as a number without a leading zero (1-31)
  • " "
  • dd - the day as a number with a leading zero (01-31)
  • " "
  • ddd - the abbreviated day name (Mon - Sun)
  • " "
  • dddd - the long day name (Monday - Sunday)
  • " "
  • M - the month as a number without a leading zero (1-12)
  • " "
  • MM - the month as a number with a leading zero (01-12)
  • " "
  • MMM - the abbreviated month name (Jan - Dec)
  • " "
  • MMMM - the long month name (January - December)
  • " "
  • yy - the year as a two digit number (00-99)
  • " "
  • yyyy - the year as a four digit number (0000-9999)
  • " "
" "

These expressions may be used for the time:" "

" "
    " "
  • h - the hour without a leading zero (0-23 or 1-12 if AM/PM display)
  • " "
  • hh - the hour with a leading zero (00-23 or 01-12 if AM/PM display)
  • " "
  • m - the minutes without a leading zero (0-59)
  • " "
  • mm - the minutes with a leading zero (00-59)
  • " "
  • s - the seconds without a leading zero (0-59)
  • " "
  • ss - the seconds with a leading zero (00-59)
  • " "
  • z - the milliseconds without leading zeroes (0-999)
  • " "
  • zzz - the milliseconds with leading zeroes (000-999)
  • " "
  • AP - switch to AM/PM display. AP will be replaced by either \"AM\" or \"PM\".
  • " "
  • ap - switch to AM/PM display. ap will be replaced by either \"am\" or \"pm\".
  • " "
  • Z - time zone in numeric form (-0500)
  • " "
" "

All other input characters will be ignored." "

"); mCustomDateFormatEdit->setWhatsThis(mCustomDateWhatsThis); radio->setWhatsThis(mCustomDateWhatsThis); gvlay->addWidget(hbox); } else { QRadioButton *radio = new QRadioButton(buttonLabel, mDateDisplayBox); gvlay->addWidget(radio); mDateDisplay->addButton(radio, dateDisplayConfig[i].dateDisplay); } } // end for loop populating mDateDisplay vlay->addWidget(mDateDisplayBox); connect(mDateDisplay, QOverload::of(&QButtonGroup::buttonClicked), this, &ConfigModuleTab::slotEmitChanged); vlay->addStretch(10); // spacer } void AppearancePageHeadersTab::slotLinkClicked(const QString &link) { if (link == QLatin1String("whatsthis1")) { QWhatsThis::showText(QCursor::pos(), mCustomDateWhatsThis); } } void AppearancePage::HeadersTab::slotSelectDefaultAggregation() { // Select current default aggregation. mAggregationComboBox->selectDefault(); } void AppearancePage::HeadersTab::slotSelectDefaultTheme() { // Select current default theme. mThemeComboBox->selectDefault(); } void AppearancePage::HeadersTab::doLoadOther() { // "General Options": loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem()); // "Aggregation": slotSelectDefaultAggregation(); // "Theme": slotSelectDefaultTheme(); // "Date Display": setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat()); } void AppearancePage::HeadersTab::doLoadFromGlobalSettings() { loadWidget(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem()); // "Aggregation": slotSelectDefaultAggregation(); // "Theme": slotSelectDefaultTheme(); setDateDisplay(MessageCore::MessageCoreSettings::self()->dateFormat(), MessageCore::MessageCoreSettings::self()->customDateFormat()); } void AppearancePage::HeadersTab::setDateDisplay(int num, const QString &format) { DateFormatter::FormatType dateDisplay = static_cast(num); // special case: needs text for the line edit: if (dateDisplay == DateFormatter::Custom) { mCustomDateFormatEdit->setText(format); } for (int i = 0; i < numDateDisplayConfig; ++i) { if (dateDisplay == dateDisplayConfig[i].dateDisplay) { mDateDisplay->button(dateDisplay)->setChecked(true); return; } } // fell through since none found: mDateDisplay->button(numDateDisplayConfig - 2)->setChecked(true); // default } void AppearancePage::HeadersTab::save() { saveCheckBox(mDisplayMessageToolTips, MessageList::MessageListSettings::self()->messageToolTipEnabledItem()); if (KMKernel::self()) { KMKernel::self()->savePaneSelection(); } // "Aggregation" mAggregationComboBox->writeDefaultConfig(); // "Theme" mThemeComboBox->writeDefaultConfig(); const int dateDisplayID = mDateDisplay->checkedId(); MessageCore::MessageCoreSettings::self()->setDateFormat(dateDisplayID); MessageCore::MessageCoreSettings::self()->setCustomDateFormat(mCustomDateFormatEdit->text()); } // // Message Window // QString AppearancePage::ReaderTab::helpAnchor() const { return QStringLiteral("configure-appearance-reader"); } AppearancePageGeneralTab::AppearancePageGeneralTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *topLayout = new QVBoxLayout(this); QGroupBox *readerBox = new QGroupBox(i18n("Message Window"), this); topLayout->addWidget(readerBox); QVBoxLayout *readerBoxLayout = new QVBoxLayout(readerBox); // "Close message window after replying or forwarding" check box: populateCheckBox(mCloseAfterReplyOrForwardCheck = new QCheckBox(this), MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem()); mCloseAfterReplyOrForwardCheck->setToolTip( i18n("Close the standalone message window after replying or forwarding the message")); readerBoxLayout->addWidget(mCloseAfterReplyOrForwardCheck); connect(mCloseAfterReplyOrForwardCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); mViewerSettings = new MessageViewer::ConfigureWidget; connect(mViewerSettings, &MessageViewer::ConfigureWidget::settingsChanged, this, &ConfigModuleTab::slotEmitChanged); readerBoxLayout->addWidget(mViewerSettings); mGravatarConfigWidget = new Gravatar::GravatarConfigWidget; connect(mGravatarConfigWidget, &Gravatar::GravatarConfigWidget::configChanged, this, &ConfigModuleTab::slotEmitChanged); readerBoxLayout->addWidget(mGravatarConfigWidget); QGroupBox *systrayBox = new QGroupBox(i18n("System Tray"), this); topLayout->addWidget(systrayBox); QVBoxLayout *systrayBoxlayout = new QVBoxLayout(systrayBox); // "Enable system tray applet" check box mSystemTrayCheck = new QCheckBox(i18n("Enable system tray icon"), this); systrayBoxlayout->addWidget(mSystemTrayCheck); // "Enable start in system tray" check box mStartInTrayCheck = new QCheckBox(i18n("Start minimized to tray")); systrayBoxlayout->addWidget(mStartInTrayCheck); // Dependencies between the two checkboxes connect(mStartInTrayCheck, &QCheckBox::stateChanged, this, [this](int state) { if (state == Qt::Checked) { mSystemTrayCheck->setCheckState(Qt::Checked); } slotEmitChanged(); }); connect(mSystemTrayCheck, &QCheckBox::stateChanged, this, [this](int state) { if (state == Qt::Unchecked) { mStartInTrayCheck->setCheckState(Qt::Unchecked); } slotEmitChanged(); }); // "Enable system tray applet" check box mShowNumberInTaskBar = new QCheckBox(i18n("Show unread email in Taskbar"), this); systrayBoxlayout->addWidget(mShowNumberInTaskBar); connect(mShowNumberInTaskBar, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); topLayout->addStretch(100); // spacer } void AppearancePage::ReaderTab::doResetToDefaultsOther() { mGravatarConfigWidget->doResetToDefaultsOther(); } void AppearancePage::ReaderTab::doLoadOther() { loadWidget(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem()); loadWidget(mStartInTrayCheck, KMailSettings::self()->startInTrayItem()); loadWidget(mShowNumberInTaskBar, KMailSettings::self()->showUnreadInTaskbarItem()); loadWidget(mCloseAfterReplyOrForwardCheck, MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem()); mViewerSettings->readConfig(); mGravatarConfigWidget->doLoadFromGlobalSettings(); } void AppearancePage::ReaderTab::save() { saveCheckBox(mSystemTrayCheck, KMailSettings::self()->systemTrayEnabledItem()); saveCheckBox(mStartInTrayCheck, KMailSettings::self()->startInTrayItem()); saveCheckBox(mShowNumberInTaskBar, KMailSettings::self()->showUnreadInTaskbarItem()); KMailSettings::self()->save(); saveCheckBox(mCloseAfterReplyOrForwardCheck, MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForwardItem()); mViewerSettings->writeConfig(); mGravatarConfigWidget->save(); } QString AppearancePage::MessageTagTab::helpAnchor() const { return QStringLiteral("configure-appearance-messagetag"); } TagListWidgetItem::TagListWidgetItem(QListWidget *parent) : QListWidgetItem(parent) , mTag(nullptr) { } TagListWidgetItem::TagListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent) : QListWidgetItem(icon, text, parent) , mTag(nullptr) { } TagListWidgetItem::~TagListWidgetItem() { } void TagListWidgetItem::setKMailTag(const MailCommon::Tag::Ptr &tag) { mTag = tag; } MailCommon::Tag::Ptr TagListWidgetItem::kmailTag() const { return mTag; } AppearancePageMessageTagTab::AppearancePageMessageTagTab(QWidget *parent) : ConfigModuleTab(parent) { mPreviousTag = -1; QHBoxLayout *maingrid = new QHBoxLayout(this); //Lefthand side Listbox and friends //Groupbox frame mTagsGroupBox = new QGroupBox(i18n("A&vailable Tags"), this); maingrid->addWidget(mTagsGroupBox); QVBoxLayout *tageditgrid = new QVBoxLayout(mTagsGroupBox); //Listbox, add, remove row QHBoxLayout *addremovegrid = new QHBoxLayout(); tageditgrid->addLayout(addremovegrid); mTagAddLineEdit = new KLineEdit(mTagsGroupBox); mTagAddLineEdit->setTrapReturnKey(true); addremovegrid->addWidget(mTagAddLineEdit); mTagAddButton = new QPushButton(mTagsGroupBox); mTagAddButton->setToolTip(i18n("Add new tag")); mTagAddButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addremovegrid->addWidget(mTagAddButton); mTagRemoveButton = new QPushButton(mTagsGroupBox); mTagRemoveButton->setToolTip(i18n("Remove selected tag")); mTagRemoveButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); addremovegrid->addWidget(mTagRemoveButton); //Up and down buttons QHBoxLayout *updowngrid = new QHBoxLayout(); tageditgrid->addLayout(updowngrid); mTagUpButton = new QPushButton(mTagsGroupBox); mTagUpButton->setToolTip(i18n("Increase tag priority")); mTagUpButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); mTagUpButton->setAutoRepeat(true); updowngrid->addWidget(mTagUpButton); mTagDownButton = new QPushButton(mTagsGroupBox); mTagDownButton->setToolTip(i18n("Decrease tag priority")); mTagDownButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); mTagDownButton->setAutoRepeat(true); updowngrid->addWidget(mTagDownButton); //Listbox for tag names QHBoxLayout *listboxgrid = new QHBoxLayout(); tageditgrid->addLayout(listboxgrid); mTagListBox = new QListWidget(mTagsGroupBox); mTagListBox->setDragDropMode(QAbstractItemView::InternalMove); connect(mTagListBox->model(), &QAbstractItemModel::rowsMoved, this, &AppearancePageMessageTagTab::slotRowsMoved); mTagListBox->setMinimumWidth(150); listboxgrid->addWidget(mTagListBox); //RHS for individual tag settings //Extra VBoxLayout for stretchers around settings QVBoxLayout *tagsettinggrid = new QVBoxLayout(); maingrid->addLayout(tagsettinggrid); //Groupbox frame mTagSettingGroupBox = new QGroupBox(i18n("Ta&g Settings"), this); tagsettinggrid->addWidget(mTagSettingGroupBox); QList actionCollections; if (kmkernel->getKMMainWidget()) { actionCollections = kmkernel->getKMMainWidget()->actionCollections(); } QHBoxLayout *lay = new QHBoxLayout(mTagSettingGroupBox); mTagWidget = new MailCommon::TagWidget(actionCollections, this); lay->addWidget(mTagWidget); connect(mTagWidget, &TagWidget::changed, this, &AppearancePageMessageTagTab::slotEmitChangeCheck); //For enabling the add button in case box is non-empty connect(mTagAddLineEdit, &KLineEdit::textChanged, this, &AppearancePage::MessageTagTab::slotAddLineTextChanged); //For on-the-fly updating of tag name in editbox connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged); connect(mTagWidget, &TagWidget::iconNameChanged, this, &AppearancePageMessageTagTab::slotIconNameChanged); connect(mTagAddLineEdit, &KLineEdit::returnPressed, this, &AppearancePageMessageTagTab::slotAddNewTag); connect(mTagAddButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotAddNewTag); connect(mTagRemoveButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotRemoveTag); connect(mTagUpButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagUp); connect(mTagDownButton, &QAbstractButton::clicked, this, &AppearancePageMessageTagTab::slotMoveTagDown); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); //Adjust widths for columns maingrid->setStretchFactor(mTagsGroupBox, 1); maingrid->setStretchFactor(lay, 1); tagsettinggrid->addStretch(10); } AppearancePageMessageTagTab::~AppearancePageMessageTagTab() { } void AppearancePage::MessageTagTab::slotEmitChangeCheck() { slotEmitChanged(); } void AppearancePage::MessageTagTab::slotRowsMoved(const QModelIndex &, int sourcestart, int sourceEnd, const QModelIndex &, int destinationRow) { Q_UNUSED(sourceEnd); Q_UNUSED(sourcestart); Q_UNUSED(destinationRow); updateButtons(); slotEmitChangeCheck(); } void AppearancePage::MessageTagTab::updateButtons() { const int currentIndex = mTagListBox->currentRow(); const bool theFirst = (currentIndex == 0); const bool theLast = (currentIndex >= (int)mTagListBox->count() - 1); const bool aFilterIsSelected = (currentIndex >= 0); mTagUpButton->setEnabled(aFilterIsSelected && !theFirst); mTagDownButton->setEnabled(aFilterIsSelected && !theLast); } void AppearancePage::MessageTagTab::slotMoveTagUp() { const int tmp_index = mTagListBox->currentRow(); if (tmp_index <= 0) { return; } swapTagsInListBox(tmp_index, tmp_index - 1); updateButtons(); } void AppearancePage::MessageTagTab::slotMoveTagDown() { const int tmp_index = mTagListBox->currentRow(); if ((tmp_index < 0) || (tmp_index >= int(mTagListBox->count()) - 1)) { return; } swapTagsInListBox(tmp_index, tmp_index + 1); updateButtons(); } void AppearancePage::MessageTagTab::swapTagsInListBox(const int first, const int second) { disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); QListWidgetItem *item = mTagListBox->takeItem(first); // now selected item is at idx(idx-1), so // insert the other item at idx, ie. above(below). mPreviousTag = second; mTagListBox->insertItem(second, item); mTagListBox->setCurrentRow(second); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); slotEmitChangeCheck(); } void AppearancePage::MessageTagTab::slotRecordTagSettings(int aIndex) { if ((aIndex < 0) || (aIndex >= int(mTagListBox->count()))) { return; } QListWidgetItem *item = mTagListBox->item(aIndex); TagListWidgetItem *tagItem = static_cast(item); MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag(); tmp_desc->tagName = tagItem->text(); mTagWidget->recordTagSettings(tmp_desc); } void AppearancePage::MessageTagTab::slotUpdateTagSettingWidgets(int aIndex) { //Check if selection is valid if ((aIndex < 0) || (mTagListBox->currentRow() < 0)) { mTagRemoveButton->setEnabled(false); mTagUpButton->setEnabled(false); mTagDownButton->setEnabled(false); mTagWidget->setEnabled(false); return; } mTagWidget->setEnabled(true); mTagRemoveButton->setEnabled(true); mTagUpButton->setEnabled((0 != aIndex)); mTagDownButton->setEnabled(((int(mTagListBox->count()) - 1) != aIndex)); QListWidgetItem *item = mTagListBox->currentItem(); TagListWidgetItem *tagItem = static_cast(item); MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag(); disconnect(mTagWidget->tagNameLineEdit(), &KLineEdit::textChanged, this, &AppearancePage::MessageTagTab::slotNameLineTextChanged); mTagWidget->tagNameLineEdit()->setEnabled(!tmp_desc->isImmutable); mTagWidget->tagNameLineEdit()->setText(tmp_desc->tagName); connect(mTagWidget->tagNameLineEdit(), &KLineEdit::textChanged, this, &AppearancePage::MessageTagTab::slotNameLineTextChanged); mTagWidget->setTagTextColor(tmp_desc->textColor); mTagWidget->setTagBackgroundColor(tmp_desc->backgroundColor); mTagWidget->setTagTextFormat(tmp_desc->isBold, tmp_desc->isItalic); mTagWidget->iconButton()->setEnabled(!tmp_desc->isImmutable); mTagWidget->iconButton()->setIcon(tmp_desc->iconName); mTagWidget->keySequenceWidget()->setEnabled(true); mTagWidget->keySequenceWidget()->setKeySequence(tmp_desc->shortcut, KKeySequenceWidget::NoValidate); mTagWidget->inToolBarCheck()->setEnabled(true); mTagWidget->inToolBarCheck()->setChecked(tmp_desc->inToolbar); } void AppearancePage::MessageTagTab::slotSelectionChanged() { mEmitChanges = false; slotRecordTagSettings(mPreviousTag); slotUpdateTagSettingWidgets(mTagListBox->currentRow()); mPreviousTag = mTagListBox->currentRow(); mEmitChanges = true; } void AppearancePage::MessageTagTab::slotRemoveTag() { const int tmp_index = mTagListBox->currentRow(); if (tmp_index >= 0) { if (KMessageBox::Yes == KMessageBox::questionYesNo(this, i18n("Do you want to remove tag \'%1\'?", mTagListBox->item(mTagListBox->currentRow())->text()))) { QListWidgetItem *item = mTagListBox->takeItem(mTagListBox->currentRow()); TagListWidgetItem *tagItem = static_cast(item); MailCommon::Tag::Ptr tmp_desc = tagItem->kmailTag(); if (tmp_desc->tag().isValid()) { new Akonadi::TagDeleteJob(tmp_desc->tag()); } else { qCWarning(KMAIL_LOG) << "Can't remove tag with invalid akonadi tag"; } mPreviousTag = -1; //Before deleting the current item, make sure the selectionChanged signal //is disconnected, so that the widgets will not get updated while the //deletion takes place. disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); delete item; connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); slotSelectionChanged(); slotEmitChangeCheck(); } } } void AppearancePage::MessageTagTab::slotDeleteTagJob(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << "Failed to delete tag " << job->errorString(); } } void AppearancePage::MessageTagTab::slotNameLineTextChanged(const QString &aText) { //If deleted all, leave the first character for the sake of not having an //empty tag name if (aText.isEmpty() && !mTagListBox->currentItem()) { return; } const int count = mTagListBox->count(); for (int i = 0; i < count; ++i) { if (mTagListBox->item(i)->text() == aText) { KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists.")); disconnect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged); mTagWidget->tagNameLineEdit()->setText(mTagListBox->currentItem()->text()); connect(mTagWidget->tagNameLineEdit(), &QLineEdit::textChanged, this, &AppearancePageMessageTagTab::slotNameLineTextChanged); return; } } //Disconnect so the tag information is not saved and reloaded with every //letter disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); mTagListBox->currentItem()->setText(aText); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); } void AppearancePage::MessageTagTab::slotIconNameChanged(const QString &iconName) { mTagListBox->currentItem()->setIcon(QIcon::fromTheme(iconName)); } void AppearancePage::MessageTagTab::slotAddLineTextChanged(const QString &aText) { mTagAddButton->setEnabled(!aText.trimmed().isEmpty()); } void AppearancePage::MessageTagTab::slotAddNewTag() { const QString newTagName = mTagAddLineEdit->text().trimmed(); if (newTagName.isEmpty()) { return; } const int count = mTagListBox->count(); for (int i = 0; i < count; ++i) { if (mTagListBox->item(i)->text() == newTagName) { KMessageBox::error(this, i18n("We cannot create tag. A tag with same name already exists.")); return; } } const int tmp_priority = mTagListBox->count(); MailCommon::Tag::Ptr tag(Tag::createDefaultTag(newTagName)); tag->priority = tmp_priority; slotEmitChangeCheck(); TagListWidgetItem *newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), newTagName, mTagListBox); newItem->setKMailTag(tag); mTagListBox->addItem(newItem); mTagListBox->setCurrentItem(newItem); mTagAddLineEdit->clear(); } void AppearancePage::MessageTagTab::doLoadFromGlobalSettings() { mTagListBox->clear(); Akonadi::TagFetchJob *fetchJob = new Akonadi::TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); connect(fetchJob, &KJob::result, this, &AppearancePageMessageTagTab::slotTagsFetched); } void AppearancePage::MessageTagTab::slotTagsFetched(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << "Failed to load tags " << job->errorString(); return; } Akonadi::TagFetchJob *fetchJob = static_cast(job); QList msgTagList; const Akonadi::Tag::List tagList = fetchJob->tags(); msgTagList.reserve(tagList.count()); for (const Akonadi::Tag &akonadiTag : tagList) { MailCommon::Tag::Ptr tag = MailCommon::Tag::fromAkonadi(akonadiTag); msgTagList.append(tag); } std::sort(msgTagList.begin(), msgTagList.end(), MailCommon::Tag::compare); for (const MailCommon::Tag::Ptr &tag : qAsConst(msgTagList)) { TagListWidgetItem *newItem = new TagListWidgetItem(QIcon::fromTheme(tag->iconName), tag->tagName, mTagListBox); newItem->setKMailTag(tag); if (tag->priority == -1) { tag->priority = mTagListBox->count() - 1; } } //Disconnect so that insertItem's do not trigger an update procedure disconnect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); connect(mTagListBox, &QListWidget::currentItemChanged, this, &AppearancePageMessageTagTab::slotSelectionChanged); slotUpdateTagSettingWidgets(-1); //Needed since the previous function doesn't affect add button mTagAddButton->setEnabled(false); // Save the original list mOriginalMsgTagList.clear(); for (const MailCommon::TagPtr &tag : qAsConst(msgTagList)) { mOriginalMsgTagList.append(MailCommon::TagPtr(new MailCommon::Tag(*tag))); } } void AppearancePage::MessageTagTab::save() { const int currentRow = mTagListBox->currentRow(); if (currentRow < 0) { return; } const int count = mTagListBox->count(); if (!count) { return; } QListWidgetItem *item = mTagListBox->currentItem(); if (!item) { return; } slotRecordTagSettings(currentRow); const int numberOfMsgTagList = count; for (int i = 0; i < numberOfMsgTagList; ++i) { TagListWidgetItem *tagItem = static_cast(mTagListBox->item(i)); if ((i >= mOriginalMsgTagList.count()) || *(tagItem->kmailTag()) != *(mOriginalMsgTagList[i])) { MailCommon::Tag::Ptr tag = tagItem->kmailTag(); tag->priority = i; Akonadi::Tag akonadiTag = tag->saveToAkonadi(); if ((*tag).id() > 0) { akonadiTag.setId((*tag).id()); } if (akonadiTag.isValid()) { new Akonadi::TagModifyJob(akonadiTag); } else { new Akonadi::TagCreateJob(akonadiTag); } } } } diff --git a/src/configuredialog/configurecomposerpage.cpp b/src/configuredialog/configurecomposerpage.cpp index a81b85605..ae5edc041 100644 --- a/src/configuredialog/configurecomposerpage.cpp +++ b/src/configuredialog/configurecomposerpage.cpp @@ -1,1276 +1,1276 @@ /* Copyright (c) 2013-2019 Montel Laurent This program 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. 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 "configurecomposerpage.h" #include "PimCommon/ConfigureImmutableWidgetUtils" using namespace PimCommon::ConfigureImmutableWidgetUtils; #include "kmkernel.h" #include "kmmainwidget.h" #include "PimCommon/AutoCorrectionWidget" #include "MessageComposer/ImageScalingWidget" #include "MessageComposer/MessageComposerSettings" #include #include "settings/kmailsettings.h" #include "configuredialog/configuredialoglistview.h" #include "PimCommon/SimpleStringListEditor" #include "templatesconfiguration_kfg.h" #include "TemplateParser/TemplatesConfiguration" #include "templateparser/customtemplates.h" #include "globalsettings_templateparser.h" #include "libkdepim/recentaddresses.h" #include #include "libkdepimakonadi/completionordereditor.h" using KPIM::RecentAddresses; #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include QString ComposerPage::helpAnchor() const { return QStringLiteral("configure-composer"); } ComposerPage::ComposerPage(QWidget *parent) : ConfigModuleWithTabs(parent) { // // "General" tab: // GeneralTab *generalTab = new GeneralTab(); addTab(generalTab, i18nc("General settings for the composer.", "General")); addConfig(KMailSettings::self(), generalTab); // // "Templates" tab: // TemplatesTab *templatesTab = new TemplatesTab(); addTab(templatesTab, i18n("Standard Templates")); // // "Custom Templates" tab: // CustomTemplatesTab *customTemplatesTab = new CustomTemplatesTab(); addTab(customTemplatesTab, i18n("Custom Templates")); // // "Subject" tab: // SubjectTab *subjectTab = new SubjectTab(); addTab(subjectTab, i18nc("Settings regarding the subject when composing a message.", "Subject")); addConfig(KMailSettings::self(), subjectTab); // // "Charset" tab: // CharsetTab *charsetTab = new CharsetTab(); addTab(charsetTab, i18n("Charset")); // // "Headers" tab: // HeadersTab *headersTab = new HeadersTab(); addTab(headersTab, i18n("Headers")); // // "Attachments" tab: // AttachmentsTab *attachmentsTab = new AttachmentsTab(); addTab(attachmentsTab, i18nc("Config->Composer->Attachments", "Attachments")); // // "autocorrection" tab: // AutoCorrectionTab *autoCorrectionTab = new AutoCorrectionTab(); addTab(autoCorrectionTab, i18n("Autocorrection")); // // "autoresize" tab: // AutoImageResizeTab *autoImageResizeTab = new AutoImageResizeTab(); addTab(autoImageResizeTab, i18n("Auto Resize Image")); } QString ComposerPage::GeneralTab::helpAnchor() const { return QStringLiteral("configure-composer-general"); } ComposerPageGeneralTab::ComposerPageGeneralTab(QWidget *parent) : ConfigModuleTab(parent) { // Main layout QGridLayout *grid = new QGridLayout(this); // "Signature" group QGroupBox *groupBox = new QGroupBox(i18nc("@title:group", "Signature")); QVBoxLayout *groupVBoxLayout = new QVBoxLayout(); // "Automatically insert signature" checkbox mAutoAppSignFileCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->autoTextSignatureItem()->label(), this); QString helpText = i18n("Automatically insert the configured signature\n" "when starting to compose a message"); mAutoAppSignFileCheck->setToolTip(helpText); mAutoAppSignFileCheck->setWhatsThis(helpText); connect(mAutoAppSignFileCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupVBoxLayout->addWidget(mAutoAppSignFileCheck); // "Insert signature above quoted text" checkbox mTopQuoteCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->prependSignatureItem()->label(), this); mTopQuoteCheck->setEnabled(false); helpText = i18n("Insert the signature above any quoted text"); mTopQuoteCheck->setToolTip(helpText); mTopQuoteCheck->setWhatsThis(helpText); connect(mTopQuoteCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mAutoAppSignFileCheck, &QAbstractButton::toggled, mTopQuoteCheck, &QWidget::setEnabled); groupVBoxLayout->addWidget(mTopQuoteCheck); // "Prepend separator to signature" checkbox mDashDashCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->dashDashSignatureItem()->label(), this); mDashDashCheck->setEnabled(false); helpText = i18n("Insert the RFC-compliant signature separator\n" "(two dashes and a space on a line) before the signature"); mDashDashCheck->setToolTip(helpText); mDashDashCheck->setWhatsThis(helpText); connect(mDashDashCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mAutoAppSignFileCheck, &QAbstractButton::toggled, mDashDashCheck, &QWidget::setEnabled); groupVBoxLayout->addWidget(mDashDashCheck); // "Remove signature when replying" checkbox mStripSignatureCheck = new QCheckBox(TemplateParser::TemplateParserSettings::self()->stripSignatureItem()->label(), this); helpText = i18n("When replying, do not quote any existing signature"); mStripSignatureCheck->setToolTip(helpText); mStripSignatureCheck->setWhatsThis(helpText); connect(mStripSignatureCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupVBoxLayout->addWidget(mStripSignatureCheck); groupVBoxLayout->addStretch(1); groupBox->setLayout(groupVBoxLayout); grid->addWidget(groupBox, 0, 0); // "Format" group groupBox = new QGroupBox(i18nc("@title:group", "Format")); QGridLayout *groupGridLayout = new QGridLayout(); int row = 0; // "Only quote selected text when replying" checkbox mQuoteSelectionOnlyCheck = new QCheckBox(MessageComposer::MessageComposerSettings::self()->quoteSelectionOnlyItem()->label(), this); helpText = i18n("When replying, only quote the selected text\n" "(instead of the complete message), if\n" "there is text selected in the message window."); mQuoteSelectionOnlyCheck->setToolTip(helpText); mQuoteSelectionOnlyCheck->setWhatsThis(helpText); connect(mQuoteSelectionOnlyCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mQuoteSelectionOnlyCheck, row, 0, 1, -1); ++row; // "Use smart quoting" checkbox mSmartQuoteCheck = new QCheckBox( TemplateParser::TemplateParserSettings::self()->smartQuoteItem()->label(), this); helpText = i18n("When replying, add quote signs in front of all lines of the quoted text,\n" "even when the line was created by adding an additional line break while\n" "word-wrapping the text."); mSmartQuoteCheck->setToolTip(helpText); mSmartQuoteCheck->setWhatsThis(helpText); connect(mSmartQuoteCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mSmartQuoteCheck, row, 0, 1, -1); ++row; // "Word wrap at column" checkbox/spinbox mWordWrapCheck = new QCheckBox( MessageComposer::MessageComposerSettings::self()->wordWrapItem()->label(), this); helpText = i18n("Enable automatic word wrapping at the specified width"); mWordWrapCheck->setToolTip(helpText); mWordWrapCheck->setWhatsThis(helpText); mWrapColumnSpin = new QSpinBox(this); mWrapColumnSpin->setMaximum(78 /*max*/); mWrapColumnSpin->setMinimum(30 /*min*/); mWrapColumnSpin->setSingleStep(1 /*step*/); mWrapColumnSpin->setValue(78 /*init*/); mWrapColumnSpin->setEnabled(false); // since !mWordWrapCheck->isChecked() helpText = i18n("Set the text width for automatic word wrapping"); mWrapColumnSpin->setToolTip(helpText); mWrapColumnSpin->setWhatsThis(helpText); connect(mWordWrapCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mWrapColumnSpin, QOverload::of(&QSpinBox::valueChanged), this, &ComposerPageGeneralTab::slotEmitChanged); // only enable the spinbox if the checkbox is checked connect(mWordWrapCheck, &QAbstractButton::toggled, mWrapColumnSpin, &QWidget::setEnabled); groupGridLayout->addWidget(mWordWrapCheck, row, 0); groupGridLayout->addWidget(mWrapColumnSpin, row, 1); ++row; // Spacing ++row; // "Reply/Forward using HTML if present" checkbox mReplyUsingHtml = new QCheckBox(TemplateParser::TemplateParserSettings::self()->replyUsingHtmlItem()->label(), this); helpText = i18n("When replying or forwarding, quote the message\n" "in the original format it was received.\n" "If unchecked, the reply will be as plain text by default."); mReplyUsingHtml->setToolTip(helpText); mReplyUsingHtml->setWhatsThis(helpText); connect(mReplyUsingHtml, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mReplyUsingHtml, row, 0, 1, -1); ++row; // "Improve plain text of HTML" checkbox mImprovePlainTextOfHtmlMessage = new QCheckBox(MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessageItem()->label(), this); // For what is supported see http://www.grantlee.org/apidox/classGrantlee_1_1PlainTextMarkupBuilder.html helpText = i18n("Format the plain text part of a message from the HTML markup.\n" "Bold, italic and underlined text, lists, and external references\n" "are supported."); mImprovePlainTextOfHtmlMessage->setToolTip(helpText); mImprovePlainTextOfHtmlMessage->setWhatsThis(helpText); connect(mImprovePlainTextOfHtmlMessage, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mImprovePlainTextOfHtmlMessage, row, 0, 1, -1); ++row; #ifdef KDEPIM_ENTERPRISE_BUILD ++row; // "Default forwarding type" combobox mForwardTypeCombo = new KComboBox(false, this); mForwardTypeCombo->addItems(QStringList() << i18nc("@item:inlistbox Inline mail forwarding", "Inline") << i18n("As Attachment")); helpText = i18n("Set the default forwarded message format"); mForwardTypeCombo->setToolTip(helpText); mForwardTypeCombo->setWhatsThis(helpText); label = new QLabel(i18n("Default forwarding type:"), this); label->setBuddy(mForwardTypeCombo); connect(mForwardTypeCombo, QOverload::of(&KComboBox::activated), this, &ComposerPageGeneralTab::slotEmitChanged); groupGridLayout->addWidget(label, row, 0); groupGridLayout->addWidget(mForwardTypeCombo, row, 1); #endif groupGridLayout->setRowStretch(row, 1); groupBox->setLayout(groupGridLayout); grid->addWidget(groupBox, 1, 0); // "Recipients" group groupBox = new QGroupBox(i18nc("@title:group", "Recipients")); groupGridLayout = new QGridLayout(); row = 0; // "Automatically request MDNs" checkbox mAutoRequestMDNCheck = new QCheckBox(KMailSettings::self()->requestMDNItem()->label(), this); helpText = i18n("By default, request an MDN when starting to compose a message.\n" "You can select this on a per-message basis using \"Options - Request Disposition Notification\""); mAutoRequestMDNCheck->setToolTip(helpText); mAutoRequestMDNCheck->setWhatsThis(helpText); connect(mAutoRequestMDNCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mAutoRequestMDNCheck, row, 0, 1, -1); ++row; // Spacing ++row; // "Use Baloo search in composer" checkbox mShowAkonadiSearchAddressesInComposer = new QCheckBox( MessageComposer::MessageComposerSettings::self()->showBalooSearchInComposerItem()->label(), this); connect(mShowAkonadiSearchAddressesInComposer, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mShowAkonadiSearchAddressesInComposer, row, 0, 1, -1); ++row; #ifdef KDEPIM_ENTERPRISE_BUILD // "Warn if too many recipients" checkbox/spinbox mRecipientCheck = new QCheckBox( GlobalSettings::self()->tooManyRecipientsItem()->label(), this); mRecipientCheck->setObjectName(QStringLiteral("kcfg_TooManyRecipients")); helpText = i18n(KMailSettings::self()->tooManyRecipientsItem()->whatsThis().toUtf8().constData()); mRecipientCheck->setWhatsThis(helpText); mRecipientCheck->setToolTip(i18n("Warn if too many recipients are specified")); mRecipientSpin = new QSpinBox(this); mRecipientSpin->setMaximum(100 /*max*/); mRecipientSpin->setMinimum(1 /*min*/); mRecipientSpin->setSingleStep(1 /*step*/); mRecipientSpin->setValue(5 /*init*/); mRecipientSpin->setObjectName(QStringLiteral("kcfg_RecipientThreshold")); mRecipientSpin->setEnabled(false); helpText = i18n(KMailSettings::self()->recipientThresholdItem()->whatsThis().toUtf8().constData()); mRecipientSpin->setWhatsThis(helpText); mRecipientSpin->setToolTip(i18n("Set the maximum number of recipients for the warning")); connect(mRecipientCheck, &QCheckBox::stateChanged, this, &ComposerPageGeneralTab::slotEmitChanged); connect(mRecipientSpin, QOverload::of(&QSpinBox::valueChanged), this, &ComposerPageGeneralTab::slotEmitChanged); // only enable the spinbox if the checkbox is checked connect(mRecipientCheck, &QCheckBox::toggled, mRecipientSpin, &QSpinBox::setEnabled); groupGridLayout->addWidget(mRecipientCheck, row, 0, 1, 2); groupGridLayout->addWidget(mRecipientSpin, row, 2); ++row; #endif // "Maximum Reply-to-All recipients" spinbox mMaximumRecipients = new QSpinBox(this); mMaximumRecipients->setMaximum(9999); mMaximumRecipients->setMinimum(0); mMaximumRecipients->setSingleStep(1); mMaximumRecipients->setValue(1); helpText = i18n("Only allow this many recipients to be specified for the message.\n" "This applies to doing a \"Reply to All\", entering recipients manually\n" "or using the \"Select...\" picker. Setting this limit helps you to\n" "avoid accidentally sending a message to too many people. Note,\n" "however, that it does not take account of distribution lists or\n" "mailing lists."); mMaximumRecipients->setToolTip(helpText); mMaximumRecipients->setWhatsThis(helpText); QLabel *label = new QLabel(MessageComposer::MessageComposerSettings::self()->maximumRecipientsItem()->label(), this); label->setBuddy(mMaximumRecipients); connect(mMaximumRecipients, QOverload::of(&QSpinBox::valueChanged), this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(label, row, 0, 1, 2); groupGridLayout->addWidget(mMaximumRecipients, row, 2); ++row; // Spacing ++row; // "Use recent addresses for autocompletion" checkbox mShowRecentAddressesInComposer = new QCheckBox( MessageComposer::MessageComposerSettings::self()->showRecentAddressesInComposerItem()->label(), this); helpText = i18n("Remember recent addresses entered,\n" "and offer them for recipient completion"); mShowRecentAddressesInComposer->setToolTip(helpText); mShowRecentAddressesInComposer->setWhatsThis(helpText); connect(mShowRecentAddressesInComposer, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(mShowRecentAddressesInComposer, row, 0, 1, -1); ++row; // "Maximum recent addresses retained" spinbox mMaximumRecentAddress = new QSpinBox(this); mMaximumRecentAddress->setMinimum(0); mMaximumRecentAddress->setMaximum(999); mMaximumRecentAddress->setSpecialValueText(i18nc("No addresses are retained", "No save")); mMaximumRecentAddress->setEnabled(false); label = new QLabel(i18n("Maximum recent addresses retained:")); label->setBuddy(mMaximumRecentAddress); label->setEnabled(false); helpText = i18n("The maximum number of recently entered addresses that will\n" "be remembered for completion"); mMaximumRecentAddress->setToolTip(helpText); mMaximumRecentAddress->setWhatsThis(helpText); connect(mMaximumRecentAddress, QOverload::of(&QSpinBox::valueChanged), this, &ConfigModuleTab::slotEmitChanged); connect(mShowRecentAddressesInComposer, &QAbstractButton::toggled, mMaximumRecentAddress, &QWidget::setEnabled); connect(mShowRecentAddressesInComposer, &QAbstractButton::toggled, label, &QWidget::setEnabled); groupGridLayout->addWidget(label, row, 0, 1, 2); groupGridLayout->addWidget(mMaximumRecentAddress, row, 2); ++row; // Configure All Address settings QPushButton *configureCompletionButton = new QPushButton(i18n("Configure Completion..."), this); connect(configureCompletionButton, &QAbstractButton::clicked, this, &ComposerPageGeneralTab::slotConfigureAddressCompletion); groupGridLayout->addWidget(configureCompletionButton, row, 1, 1, 2); groupGridLayout->setRowStretch(row, 1); groupBox->setLayout(groupGridLayout); grid->addWidget(groupBox, 0, 1); groupBox = new QGroupBox(i18nc("@title:group", "Autosave")); groupGridLayout = new QGridLayout(); row = 0; // "Autosave interval" spinbox mAutoSave = new KPluralHandlingSpinBox(this); mAutoSave->setMaximum(60); mAutoSave->setMinimum(0); mAutoSave->setSingleStep(1); mAutoSave->setValue(1); mAutoSave->setObjectName(QStringLiteral("kcfg_AutosaveInterval")); mAutoSave->setSpecialValueText(i18n("No autosave")); mAutoSave->setSuffix(ki18ncp("Interval suffix", " minute", " minutes")); helpText = i18n("Automatically save the message at this specified interval"); mAutoSave->setToolTip(helpText); mAutoSave->setWhatsThis(helpText); label = new QLabel(KMailSettings::self()->autosaveIntervalItem()->label(), this); label->setBuddy(mAutoSave); connect(mAutoSave, QOverload::of(&QSpinBox::valueChanged), this, &ConfigModuleTab::slotEmitChanged); groupGridLayout->addWidget(label, row, 0); groupGridLayout->addWidget(mAutoSave, row, 1); row++; groupGridLayout->setRowStretch(row, 1); groupBox->setLayout(groupGridLayout); grid->addWidget(groupBox, 1, 1); grid->setRowStretch(2, 1); } void ComposerPage::GeneralTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); const bool autoAppSignFile = MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto"); const bool topQuoteCheck = MessageComposer::MessageComposerSettings::self()->prependSignature(); const bool dashDashSignature = MessageComposer::MessageComposerSettings::self()->dashDashSignature(); const bool smartQuoteCheck = MessageComposer::MessageComposerSettings::self()->quoteSelectionOnly(); const bool wordWrap = MessageComposer::MessageComposerSettings::self()->wordWrap(); const int wrapColumn = MessageComposer::MessageComposerSettings::self()->lineWrapWidth(); const bool showRecentAddress = MessageComposer::MessageComposerSettings::self()->showRecentAddressesInComposer(); const int maximumRecipient = MessageComposer::MessageComposerSettings::self()->maximumRecipients(); const bool improvePlainText = MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessage(); const bool showBalooSearchInComposer = MessageComposer::MessageComposerSettings::self()->showBalooSearchInComposer(); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); mAutoAppSignFileCheck->setChecked(autoAppSignFile); mTopQuoteCheck->setChecked(topQuoteCheck); mDashDashCheck->setChecked(dashDashSignature); mQuoteSelectionOnlyCheck->setChecked(smartQuoteCheck); mWordWrapCheck->setChecked(wordWrap); mWrapColumnSpin->setValue(wrapColumn); mMaximumRecipients->setValue(maximumRecipient); mShowRecentAddressesInComposer->setChecked(showRecentAddress); mShowAkonadiSearchAddressesInComposer->setChecked(showBalooSearchInComposer); mImprovePlainTextOfHtmlMessage->setChecked(improvePlainText); mMaximumRecentAddress->setValue(200); } void ComposerPage::GeneralTab::doLoadFromGlobalSettings() { // various check boxes: mAutoAppSignFileCheck->setChecked( MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")); loadWidget(mTopQuoteCheck, MessageComposer::MessageComposerSettings::self()->prependSignatureItem()); loadWidget(mDashDashCheck, MessageComposer::MessageComposerSettings::self()->dashDashSignatureItem()); loadWidget(mSmartQuoteCheck, TemplateParser::TemplateParserSettings::self()->smartQuoteItem()); loadWidget(mQuoteSelectionOnlyCheck, MessageComposer::MessageComposerSettings::self()->quoteSelectionOnlyItem()); loadWidget(mReplyUsingHtml, TemplateParser::TemplateParserSettings::self()->replyUsingHtmlItem()); loadWidget(mStripSignatureCheck, TemplateParser::TemplateParserSettings::self()->stripSignatureItem()); loadWidget(mAutoRequestMDNCheck, KMailSettings::self()->requestMDNItem()); loadWidget(mWordWrapCheck, MessageComposer::MessageComposerSettings::self()->wordWrapItem()); loadWidget(mWrapColumnSpin, MessageComposer::MessageComposerSettings::self()->lineWrapWidthItem()); loadWidget(mMaximumRecipients, MessageComposer::MessageComposerSettings::self()->maximumRecipientsItem()); mAutoSave->setValue(KMailSettings::self()->autosaveInterval()); loadWidget(mShowRecentAddressesInComposer, MessageComposer::MessageComposerSettings::self()->showRecentAddressesInComposerItem()); loadWidget(mShowAkonadiSearchAddressesInComposer, MessageComposer::MessageComposerSettings::self()->showBalooSearchInComposerItem()); mImprovePlainTextOfHtmlMessage->setChecked(MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessage()); #ifdef KDEPIM_ENTERPRISE_BUILD mRecipientCheck->setChecked(KMailSettings::self()->tooManyRecipients()); mRecipientSpin->setValue(KMailSettings::self()->recipientThreshold()); if (KMailSettings::self()->forwardingInlineByDefault()) { mForwardTypeCombo->setCurrentIndex(0); } else { mForwardTypeCombo->setCurrentIndex(1); } #endif mMaximumRecentAddress->setValue(RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->maxCount()); } void ComposerPage::GeneralTab::save() { saveCheckBox(mTopQuoteCheck, MessageComposer::MessageComposerSettings::self()->prependSignatureItem()); saveCheckBox(mDashDashCheck, MessageComposer::MessageComposerSettings::self()->dashDashSignatureItem()); saveCheckBox(mSmartQuoteCheck, TemplateParser::TemplateParserSettings::self()->smartQuoteItem()); saveCheckBox(mQuoteSelectionOnlyCheck, MessageComposer::MessageComposerSettings::self()->quoteSelectionOnlyItem()); saveCheckBox(mReplyUsingHtml, TemplateParser::TemplateParserSettings::self()->replyUsingHtmlItem()); saveCheckBox(mStripSignatureCheck, TemplateParser::TemplateParserSettings::self()->stripSignatureItem()); saveCheckBox(mAutoRequestMDNCheck, KMailSettings::self()->requestMDNItem()); saveCheckBox(mWordWrapCheck, MessageComposer::MessageComposerSettings::self()->wordWrapItem()); MessageComposer::MessageComposerSettings::self()->setAutoTextSignature( mAutoAppSignFileCheck->isChecked() ? QStringLiteral("auto") : QStringLiteral("manual")); saveSpinBox(mWrapColumnSpin, MessageComposer::MessageComposerSettings::self()->lineWrapWidthItem()); saveSpinBox(mMaximumRecipients, MessageComposer::MessageComposerSettings::self()->maximumRecipientsItem()); KMailSettings::self()->setAutosaveInterval(mAutoSave->value()); MessageComposer::MessageComposerSettings::self()->setShowRecentAddressesInComposer(mShowRecentAddressesInComposer->isChecked()); MessageComposer::MessageComposerSettings::self()->setShowBalooSearchInComposer(mShowAkonadiSearchAddressesInComposer->isChecked()); MessageComposer::MessageComposerSettings::self()->setImprovePlainTextOfHtmlMessage(mImprovePlainTextOfHtmlMessage->isChecked()); #ifdef KDEPIM_ENTERPRISE_BUILD KMailSettings::self()->setTooManyRecipients(mRecipientCheck->isChecked()); KMailSettings::self()->setRecipientThreshold(mRecipientSpin->value()); KMailSettings::self()->setForwardingInlineByDefault(mForwardTypeCombo->currentIndex() == 0); #endif RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->setMaxCount(mMaximumRecentAddress->value()); MessageComposer::MessageComposerSettings::self()->requestSync(); } void ComposerPage::GeneralTab::slotConfigureAddressCompletion() { KLDAP::LdapClientSearch search; QPointer dlg(new KPIM::CompletionConfigureDialog(this)); dlg->setRecentAddresses(KPIM::RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->addresses()); dlg->setLdapClientSearch(&search); KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimbalooblacklist")); KConfigGroup group(config, "AddressLineEdit"); const QStringList balooBlackList = group.readEntry("BalooBackList", QStringList()); dlg->setEmailBlackList(balooBlackList); dlg->load(); if (dlg->exec()) { if (dlg->recentAddressWasChanged()) { KPIM::RecentAddresses::self(MessageComposer::MessageComposerSettings::self()->config())->clear(); dlg->storeAddresses(MessageComposer::MessageComposerSettings::self()->config()); } } delete dlg; } QString ComposerPage::TemplatesTab::helpAnchor() const { return QStringLiteral("configure-composer-templates"); } ComposerPageTemplatesTab::ComposerPageTemplatesTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); mWidget = new TemplateParser::TemplatesConfiguration(this); vlay->addWidget(mWidget); connect(mWidget, &TemplateParser::TemplatesConfiguration::changed, this, &ConfigModuleTab::slotEmitChanged); } void ComposerPage::TemplatesTab::doLoadFromGlobalSettings() { mWidget->loadFromGlobal(); } void ComposerPage::TemplatesTab::save() { mWidget->saveToGlobal(); } void ComposerPage::TemplatesTab::doResetToDefaultsOther() { mWidget->resetToDefault(); } QString ComposerPage::CustomTemplatesTab::helpAnchor() const { return QStringLiteral("configure-composer-custom-templates"); } ComposerPageCustomTemplatesTab::ComposerPageCustomTemplatesTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); mWidget = new TemplateParser::CustomTemplates(kmkernel->getKMMainWidget() ? kmkernel->getKMMainWidget()->actionCollections() : QList(), this); vlay->addWidget(mWidget); connect(mWidget, &TemplateParser::CustomTemplates::changed, this, &ConfigModuleTab::slotEmitChanged); if (KMKernel::self()) { connect(mWidget, &TemplateParser::CustomTemplates::templatesUpdated, KMKernel::self(), &KMKernel::updatedTemplates); } } void ComposerPage::CustomTemplatesTab::doLoadFromGlobalSettings() { mWidget->load(); } void ComposerPage::CustomTemplatesTab::save() { mWidget->save(); } QString ComposerPage::SubjectTab::helpAnchor() const { return QStringLiteral("configure-composer-subject"); } ComposerPageSubjectTab::ComposerPageSubjectTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); QGroupBox *group = new QGroupBox(i18n("Repl&y Subject Prefixes"), this); QLayout *layout = new QVBoxLayout(group); // row 0: help text: QLabel *label = new QLabel(i18n("Recognize any sequence of the following prefixes\n" "(entries are case-insensitive regular expressions):"), group); label->setWordWrap(true); label->setAlignment(Qt::AlignLeft); // row 1, string list editor: PimCommon::SimpleStringListEditor::ButtonCode buttonCode = static_cast(PimCommon::SimpleStringListEditor::Add | PimCommon::SimpleStringListEditor::Remove | PimCommon::SimpleStringListEditor::Modify); mReplyListEditor = new PimCommon::SimpleStringListEditor(group, buttonCode, i18n("A&dd..."), i18n("Re&move"), i18n("Mod&ify..."), i18n("Enter new reply prefix:")); connect(mReplyListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); // row 2: "replace [...]" check box: mReplaceReplyPrefixCheck = new QCheckBox( MessageCore::MessageCoreSettings::self()->replaceReplyPrefixItem()->label(), group); connect(mReplaceReplyPrefixCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); layout->addWidget(label); layout->addWidget(mReplyListEditor); layout->addWidget(mReplaceReplyPrefixCheck); vlay->addWidget(group); group = new QGroupBox(i18n("For&ward Subject Prefixes"), this); layout = new QVBoxLayout(group); // row 0: help text: label = new QLabel(i18n("Recognize any sequence of the following prefixes\n" "(entries are case-insensitive regular expressions):"), group); label->setAlignment(Qt::AlignLeft); label->setWordWrap(true); // row 1: string list editor mForwardListEditor = new PimCommon::SimpleStringListEditor(group, buttonCode, i18n("Add..."), i18n("Remo&ve"), i18n("Modify..."), i18n("Enter new forward prefix:")); connect(mForwardListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); // row 3: "replace [...]" check box: mReplaceForwardPrefixCheck = new QCheckBox( MessageCore::MessageCoreSettings::self()->replaceForwardPrefixItem()->label(), group); connect(mReplaceForwardPrefixCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); layout->addWidget(label); layout->addWidget(mForwardListEditor); layout->addWidget(mReplaceForwardPrefixCheck); vlay->addWidget(group); } void ComposerPage::SubjectTab::doLoadFromGlobalSettings() { loadWidget(mReplyListEditor, MessageCore::MessageCoreSettings::self()->replyPrefixesItem()); loadWidget(mForwardListEditor, MessageCore::MessageCoreSettings::self()->forwardPrefixesItem()); loadWidget(mReplaceForwardPrefixCheck, MessageCore::MessageCoreSettings::self()->replaceForwardPrefixItem()); loadWidget(mReplaceReplyPrefixCheck, MessageCore::MessageCoreSettings::self()->replaceReplyPrefixItem()); } void ComposerPage::SubjectTab::save() { saveSimpleStringListEditor(mReplyListEditor, MessageCore::MessageCoreSettings::self()->replyPrefixesItem()); saveSimpleStringListEditor(mForwardListEditor, MessageCore::MessageCoreSettings::self()->forwardPrefixesItem()); saveCheckBox(mReplaceForwardPrefixCheck, MessageCore::MessageCoreSettings::self()->replaceForwardPrefixItem()); saveCheckBox(mReplaceReplyPrefixCheck, MessageCore::MessageCoreSettings::self()->replaceReplyPrefixItem()); } void ComposerPage::SubjectTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); loadWidget(mReplyListEditor, MessageCore::MessageCoreSettings::self()->replyPrefixesItem()); loadWidget(mForwardListEditor, MessageCore::MessageCoreSettings::self()->forwardPrefixesItem()); loadWidget(mReplaceForwardPrefixCheck, MessageCore::MessageCoreSettings::self()->replaceForwardPrefixItem()); loadWidget(mReplaceReplyPrefixCheck, MessageCore::MessageCoreSettings::self()->replaceReplyPrefixItem()); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); } QString ComposerPage::CharsetTab::helpAnchor() const { return QStringLiteral("configure-composer-charset"); } ComposerPageCharsetTab::ComposerPageCharsetTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); QLabel *label = new QLabel(i18n("This list is checked for every outgoing message " "from the top to the bottom for a charset that " "contains all required characters."), this); label->setWordWrap(true); vlay->addWidget(label); mCharsetListEditor = new PimCommon::SimpleStringListEditor(this, PimCommon::SimpleStringListEditor::All, i18n("A&dd..."), i18n("Remo&ve"), i18n("&Modify..."), i18n("Enter charset:")); mCharsetListEditor->setUpDownAutoRepeat(true); connect(mCharsetListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mCharsetListEditor, 1); mKeepReplyCharsetCheck = new QCheckBox(i18n("&Keep original charset when " "replying or forwarding (if " "possible)"), this); connect(mKeepReplyCharsetCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mKeepReplyCharsetCheck); connect(mCharsetListEditor, &PimCommon::SimpleStringListEditor::aboutToAdd, this, &ComposerPageCharsetTab::slotVerifyCharset); setEnabled(kmkernel); } void ComposerPage::CharsetTab::slotVerifyCharset(QString &charset) { if (charset.isEmpty()) { return; } // KCharsets::codecForName("us-ascii") returns "iso-8859-1" (cf. Bug #49812) // therefore we have to treat this case specially const QString charsetLower = charset.toLower(); if (charsetLower == QLatin1String("us-ascii")) { charset = QStringLiteral("us-ascii"); return; } else if (charsetLower == QLatin1String("locale")) { charset = QStringLiteral("%1 (locale)") .arg(QString::fromLatin1(kmkernel->networkCodec()->name()).toLower()); return; } bool ok = false; QTextCodec *codec = KCharsets::charsets()->codecForName(charset, ok); if (ok && codec) { charset = QString::fromLatin1(codec->name()).toLower(); return; } KMessageBox::sorry(this, i18n("This charset is not supported.")); charset.clear(); } void ComposerPage::CharsetTab::doLoadOther() { if (!kmkernel) { return; } QStringList charsets = MessageComposer::MessageComposerSettings::preferredCharsets(); QStringList::Iterator end(charsets.end()); for (QStringList::Iterator it = charsets.begin(); it != end; ++it) { if ((*it) == QLatin1String("locale")) { QByteArray cset = kmkernel->networkCodec()->name(); cset = cset.toLower(); (*it) = QStringLiteral("%1 (locale)").arg(QString::fromLatin1(cset)); } } mCharsetListEditor->setStringList(charsets); loadWidget(mKeepReplyCharsetCheck, MessageComposer::MessageComposerSettings::self()->forceReplyCharsetItem()); } void ComposerPage::CharsetTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); mCharsetListEditor->setStringList(MessageComposer::MessageComposerSettings::preferredCharsets()); mKeepReplyCharsetCheck->setChecked(MessageComposer::MessageComposerSettings::forceReplyCharset()); saveCheckBox(mKeepReplyCharsetCheck, MessageComposer::MessageComposerSettings::self()->forceReplyCharsetItem()); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); slotEmitChanged(); } void ComposerPage::CharsetTab::save() { if (!kmkernel) { return; } QStringList charsetList = mCharsetListEditor->stringList(); QStringList::Iterator it = charsetList.begin(); QStringList::Iterator end = charsetList.end(); for (; it != end; ++it) { if ((*it).endsWith(QLatin1String("(locale)"))) { (*it) = QStringLiteral("locale"); } } MessageComposer::MessageComposerSettings::setPreferredCharsets(charsetList); saveCheckBox(mKeepReplyCharsetCheck, MessageComposer::MessageComposerSettings::self()->forceReplyCharsetItem()); } QString ComposerPage::HeadersTab::helpAnchor() const { return QStringLiteral("configure-composer-headers"); } ComposerPageHeadersTab::ComposerPageHeadersTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "Use custom Message-Id suffix" checkbox: mCreateOwnMessageIdCheck = new QCheckBox(i18n("&Use custom message-id suffix"), this); connect(mCreateOwnMessageIdCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mCreateOwnMessageIdCheck); // "Message-Id suffix" line edit and label: QHBoxLayout *hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mMessageIdSuffixEdit = new QLineEdit(this); mMessageIdSuffixEdit->setClearButtonEnabled(true); // only ASCII letters, digits, plus, minus and dots are allowed QRegularExpressionValidator *messageIdSuffixValidator = new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[a-zA-Z0-9+-]+(?:\\.[a-zA-Z0-9+-]+)*")), this); mMessageIdSuffixEdit->setValidator(messageIdSuffixValidator); QLabel *label = new QLabel(i18n("Custom message-&id suffix:"), this); label->setBuddy(mMessageIdSuffixEdit); label->setEnabled(false); // since !mCreateOwnMessageIdCheck->isChecked() mMessageIdSuffixEdit->setEnabled(false); hlay->addWidget(label); hlay->addWidget(mMessageIdSuffixEdit, 1); connect(mCreateOwnMessageIdCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled); connect(mCreateOwnMessageIdCheck, &QAbstractButton::toggled, mMessageIdSuffixEdit, &QWidget::setEnabled); connect(mMessageIdSuffixEdit, &QLineEdit::textChanged, this, &ConfigModuleTab::slotEmitChanged); // horizontal rule and "custom header fields" label: vlay->addWidget(new KSeparator(Qt::Horizontal, this)); vlay->addWidget(new QLabel(i18n("Define custom mime header fields:"), this)); // "custom header fields" listbox: QGridLayout *glay = new QGridLayout(); // inherits spacing vlay->addLayout(glay); glay->setRowStretch(2, 1); glay->setColumnStretch(1, 1); mHeaderList = new ListView(this); mHeaderList->setHeaderLabels(QStringList() << i18nc("@title:column Name of the mime header.", "Name") << i18nc("@title:column Value of the mimeheader.", "Value")); mHeaderList->setSortingEnabled(false); connect(mHeaderList, &QTreeWidget::currentItemChanged, this, &ComposerPageHeadersTab::slotMimeHeaderSelectionChanged); connect(mHeaderList, &ListView::addHeader, this, &ComposerPageHeadersTab::slotNewMimeHeader); connect(mHeaderList, &ListView::removeHeader, this, &ComposerPageHeadersTab::slotRemoveMimeHeader); glay->addWidget(mHeaderList, 0, 0, 3, 2); // "new" and "remove" buttons: QPushButton *button = new QPushButton(i18nc("@action:button Add new mime header field.", "Ne&w"), this); connect(button, &QAbstractButton::clicked, this, &ComposerPageHeadersTab::slotNewMimeHeader); button->setAutoDefault(false); glay->addWidget(button, 0, 2); mRemoveHeaderButton = new QPushButton(i18n("Re&move"), this); connect(mRemoveHeaderButton, &QAbstractButton::clicked, this, &ComposerPageHeadersTab::slotRemoveMimeHeader); button->setAutoDefault(false); glay->addWidget(mRemoveHeaderButton, 1, 2); // "name" and "value" line edits and labels: mTagNameEdit = new QLineEdit(this); mTagNameEdit->setClearButtonEnabled(true); mTagNameEdit->setEnabled(false); mTagNameLabel = new QLabel(i18nc("@label:textbox Name of the mime header.", "&Name:"), this); mTagNameLabel->setBuddy(mTagNameEdit); mTagNameLabel->setEnabled(false); glay->addWidget(mTagNameLabel, 3, 0); glay->addWidget(mTagNameEdit, 3, 1); connect(mTagNameEdit, &QLineEdit::textChanged, this, &ComposerPageHeadersTab::slotMimeHeaderNameChanged); mTagValueEdit = new QLineEdit(this); mTagValueEdit->setClearButtonEnabled(true); mTagValueEdit->setEnabled(false); mTagValueLabel = new QLabel(i18n("&Value:"), this); mTagValueLabel->setBuddy(mTagValueEdit); mTagValueLabel->setEnabled(false); glay->addWidget(mTagValueLabel, 4, 0); glay->addWidget(mTagValueEdit, 4, 1); connect(mTagValueEdit, &QLineEdit::textChanged, this, &ComposerPageHeadersTab::slotMimeHeaderValueChanged); } void ComposerPage::HeadersTab::slotMimeHeaderSelectionChanged() { mEmitChanges = false; QTreeWidgetItem *item = mHeaderList->currentItem(); if (item) { mTagNameEdit->setText(item->text(0)); mTagValueEdit->setText(item->text(1)); } else { mTagNameEdit->clear(); mTagValueEdit->clear(); } mRemoveHeaderButton->setEnabled(item); mTagNameEdit->setEnabled(item); mTagValueEdit->setEnabled(item); mTagNameLabel->setEnabled(item); mTagValueLabel->setEnabled(item); mEmitChanges = true; } void ComposerPage::HeadersTab::slotMimeHeaderNameChanged(const QString &text) { // is called on ::setup(), when clearing the line edits. So be // prepared to not find a selection: QTreeWidgetItem *item = mHeaderList->currentItem(); if (item) { item->setText(0, text); } slotEmitChanged(); } void ComposerPage::HeadersTab::slotMimeHeaderValueChanged(const QString &text) { // is called on ::setup(), when clearing the line edits. So be // prepared to not find a selection: QTreeWidgetItem *item = mHeaderList->currentItem(); if (item) { item->setText(1, text); } slotEmitChanged(); } void ComposerPage::HeadersTab::slotNewMimeHeader() { QTreeWidgetItem *listItem = new QTreeWidgetItem(mHeaderList); mHeaderList->setCurrentItem(listItem); slotEmitChanged(); } void ComposerPage::HeadersTab::slotRemoveMimeHeader() { // calling this w/o selection is a programming error: QTreeWidgetItem *item = mHeaderList->currentItem(); if (!item) { qCDebug(KMAIL_LOG) << "==================================================" << "Error: Remove button was pressed although no custom header was selected\n" << "==================================================\n"; return; } QTreeWidgetItem *below = mHeaderList->itemBelow(item); if (below) { qCDebug(KMAIL_LOG) << "below"; mHeaderList->setCurrentItem(below); delete item; item = nullptr; } else if (mHeaderList->topLevelItemCount() > 0) { delete item; item = nullptr; mHeaderList->setCurrentItem( mHeaderList->topLevelItem(mHeaderList->topLevelItemCount() - 1) ); } slotEmitChanged(); } void ComposerPage::HeadersTab::doLoadOther() { mMessageIdSuffixEdit->setText(MessageComposer::MessageComposerSettings::customMsgIDSuffix()); const bool state = (!MessageComposer::MessageComposerSettings::customMsgIDSuffix().isEmpty() && MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()); mCreateOwnMessageIdCheck->setChecked(state); mHeaderList->clear(); mTagNameEdit->clear(); mTagValueEdit->clear(); QTreeWidgetItem *item = nullptr; const int count = KMailSettings::self()->customMessageHeadersCount(); for (int i = 0; i < count; ++i) { KConfigGroup config(KMKernel::self()->config(), QLatin1String("Mime #") + QString::number(i)); const QString name = config.readEntry("name"); const QString value = config.readEntry("value"); if (!name.isEmpty()) { item = new QTreeWidgetItem(mHeaderList, item); item->setText(0, name); item->setText(1, value); } } if (mHeaderList->topLevelItemCount() > 0) { mHeaderList->setCurrentItem(mHeaderList->topLevelItem(0)); } else { // disable the "Remove" button mRemoveHeaderButton->setEnabled(false); } } void ComposerPage::HeadersTab::save() { MessageComposer::MessageComposerSettings::self()->setCustomMsgIDSuffix(mMessageIdSuffixEdit->text()); MessageComposer::MessageComposerSettings::self()->setUseCustomMessageIdSuffix(mCreateOwnMessageIdCheck->isChecked()); //Clean config const int oldHeadersCount = KMailSettings::self()->customMessageHeadersCount(); for (int i = 0; i < oldHeadersCount; ++i) { const QString groupMimeName = QStringLiteral("Mime #%1").arg(i); if (KMKernel::self()->config()->hasGroup(groupMimeName)) { KConfigGroup config(KMKernel::self()->config(), groupMimeName); config.deleteGroup(); } } int numValidEntries = 0; QTreeWidgetItem *item = nullptr; const int numberOfEntry = mHeaderList->topLevelItemCount(); for (int i = 0; i < numberOfEntry; ++i) { item = mHeaderList->topLevelItem(i); const QString str = item->text(0).trimmed(); if (!str.isEmpty()) { if (str == QLatin1String("Content-Type")) { KMessageBox::error(this, i18n("\'Content-Type\' is not an authorized string. This header will be not saved."), i18n("Invalid header")); continue; } KConfigGroup config(KMKernel::self()->config(), QStringLiteral("Mime #%1").arg(numValidEntries)); config.writeEntry("name", str); config.writeEntry("value", item->text(1)); numValidEntries++; } } KMailSettings::self()->setCustomMessageHeadersCount(numValidEntries); } void ComposerPage::HeadersTab::doResetToDefaultsOther() { const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true); const QString messageIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix(); const bool useCustomMessageIdSuffix = MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix(); MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults); mMessageIdSuffixEdit->setText(messageIdSuffix); const bool state = (!messageIdSuffix.isEmpty() && useCustomMessageIdSuffix); mCreateOwnMessageIdCheck->setChecked(state); mHeaderList->clear(); mTagNameEdit->clear(); mTagValueEdit->clear(); // disable the "Remove" button mRemoveHeaderButton->setEnabled(false); } QString ComposerPage::AttachmentsTab::helpAnchor() const { return QStringLiteral("configure-composer-attachments"); } ComposerPageAttachmentsTab::ComposerPageAttachmentsTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); // "Outlook compatible attachment naming" check box mOutlookCompatibleCheck = new QCheckBox(i18n("Outlook-compatible attachment naming"), this); mOutlookCompatibleCheck->setChecked(false); mOutlookCompatibleCheck->setToolTip(i18n( "Turn this option on to make Outlook(tm) understand attachment names " "containing non-English characters")); connect(mOutlookCompatibleCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); connect(mOutlookCompatibleCheck, &QAbstractButton::clicked, this, &ComposerPageAttachmentsTab::slotOutlookCompatibleClicked); vlay->addWidget(mOutlookCompatibleCheck); vlay->addSpacing(5); // "Enable detection of missing attachments" check box mMissingAttachmentDetectionCheck = new QCheckBox(i18n("E&nable detection of missing attachments"), this); mMissingAttachmentDetectionCheck->setChecked(true); connect(mMissingAttachmentDetectionCheck, &QCheckBox::stateChanged, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mMissingAttachmentDetectionCheck); // "Attachment key words" label and string list editor QLabel *label = new QLabel(i18n("Recognize any of the following key words as " "intention to attach a file:"), this); label->setAlignment(Qt::AlignLeft); label->setWordWrap(true); vlay->addWidget(label); PimCommon::SimpleStringListEditor::ButtonCode buttonCode = static_cast(PimCommon::SimpleStringListEditor::Add | PimCommon::SimpleStringListEditor::Remove | PimCommon::SimpleStringListEditor::Modify); mAttachWordsListEditor = new PimCommon::SimpleStringListEditor(this, buttonCode, i18n("A&dd..."), i18n("Re&move"), i18n("Mod&ify..."), i18n("Enter new key word:")); connect(mAttachWordsListEditor, &PimCommon::SimpleStringListEditor::changed, this, &ConfigModuleTab::slotEmitChanged); vlay->addWidget(mAttachWordsListEditor); connect(mMissingAttachmentDetectionCheck, &QAbstractButton::toggled, label, &QWidget::setEnabled); connect(mMissingAttachmentDetectionCheck, &QAbstractButton::toggled, mAttachWordsListEditor, &QWidget::setEnabled); QHBoxLayout *layAttachment = new QHBoxLayout; label = new QLabel(i18n("Maximum Attachment Size:"), this); label->setAlignment(Qt::AlignLeft); layAttachment->addWidget(label); mMaximumAttachmentSize = new QSpinBox(this); mMaximumAttachmentSize->setRange(-1, 99999); mMaximumAttachmentSize->setSingleStep(100); mMaximumAttachmentSize->setSuffix(i18nc("spinbox suffix: unit for kilobyte", " kB")); connect(mMaximumAttachmentSize, QOverload::of(&QSpinBox::valueChanged), this, &ConfigModuleTab::slotEmitChanged); mMaximumAttachmentSize->setSpecialValueText(i18n("No limit")); layAttachment->addWidget(mMaximumAttachmentSize); vlay->addLayout(layAttachment); } void ComposerPage::AttachmentsTab::doLoadFromGlobalSettings() { loadWidget(mOutlookCompatibleCheck, MessageComposer::MessageComposerSettings::self()->outlookCompatibleAttachmentsItem()); loadWidget(mMissingAttachmentDetectionCheck, KMailSettings::self()->showForgottenAttachmentWarningItem()); loadWidget(mAttachWordsListEditor, KMailSettings::self()->attachmentKeywordsItem()); const int maximumAttachmentSize(MessageCore::MessageCoreSettings::self()->maximumAttachmentSize()); mMaximumAttachmentSize->setValue(maximumAttachmentSize == -1 ? -1 : MessageCore::MessageCoreSettings::self()->maximumAttachmentSize() / 1024); } void ComposerPage::AttachmentsTab::save() { saveCheckBox(mOutlookCompatibleCheck, MessageComposer::MessageComposerSettings::self()->outlookCompatibleAttachmentsItem()); saveCheckBox(mMissingAttachmentDetectionCheck, KMailSettings::self()->showForgottenAttachmentWarningItem()); saveSimpleStringListEditor(mAttachWordsListEditor, KMailSettings::self()->attachmentKeywordsItem()); KMime::setUseOutlookAttachmentEncoding(mOutlookCompatibleCheck->isChecked()); const int maximumAttachmentSize(mMaximumAttachmentSize->value()); MessageCore::MessageCoreSettings::self()->setMaximumAttachmentSize(maximumAttachmentSize == -1 ? -1 : maximumAttachmentSize * 1024); } void ComposerPageAttachmentsTab::slotOutlookCompatibleClicked() { if (mOutlookCompatibleCheck->isChecked()) { KMessageBox::information(nullptr, i18n("You have chosen to " "encode attachment names containing non-English characters in a way that " "is understood by Outlook(tm) and other mail clients that do not " "support standard-compliant encoded attachment names.\n" "Note that KMail may create non-standard compliant messages, " "and consequently it is possible that your messages will not be " "understood by standard-compliant mail clients; so, unless you have no " "other choice, you should not enable this option.")); } } ComposerPageAutoCorrectionTab::ComposerPageAutoCorrectionTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setSpacing(0); - vlay->setMargin(0); + vlay->setContentsMargins(0, 0, 0, 0); autocorrectionWidget = new PimCommon::AutoCorrectionWidget(this); if (KMKernel::self()) { autocorrectionWidget->setAutoCorrection(KMKernel::self()->composerAutoCorrection()); } vlay->addWidget(autocorrectionWidget); connect(autocorrectionWidget, &PimCommon::AutoCorrectionWidget::changed, this, &ConfigModuleTab::slotEmitChanged); } QString ComposerPageAutoCorrectionTab::helpAnchor() const { return QStringLiteral("configure-autocorrection"); } void ComposerPageAutoCorrectionTab::save() { autocorrectionWidget->writeConfig(); } void ComposerPageAutoCorrectionTab::doLoadFromGlobalSettings() { autocorrectionWidget->loadConfig(); } void ComposerPageAutoCorrectionTab::doResetToDefaultsOther() { autocorrectionWidget->resetToDefault(); } ComposerPageAutoImageResizeTab::ComposerPageAutoImageResizeTab(QWidget *parent) : ConfigModuleTab(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setSpacing(0); - vlay->setMargin(0); + vlay->setContentsMargins(0, 0, 0, 0); autoResizeWidget = new MessageComposer::ImageScalingWidget(this); vlay->addWidget(autoResizeWidget); connect(autoResizeWidget, &MessageComposer::ImageScalingWidget::changed, this, &ConfigModuleTab::slotEmitChanged); } QString ComposerPageAutoImageResizeTab::helpAnchor() const { return QStringLiteral("configure-image-resize"); } void ComposerPageAutoImageResizeTab::save() { autoResizeWidget->writeConfig(); } void ComposerPageAutoImageResizeTab::doLoadFromGlobalSettings() { autoResizeWidget->loadConfig(); } void ComposerPageAutoImageResizeTab::doResetToDefaultsOther() { autoResizeWidget->resetToDefault(); } diff --git a/src/configuredialog/configuredialog_p.cpp b/src/configuredialog/configuredialog_p.cpp index b2fece48d..a7805cb0d 100644 --- a/src/configuredialog/configuredialog_p.cpp +++ b/src/configuredialog/configuredialog_p.cpp @@ -1,100 +1,100 @@ // configuredialog_p.cpp: classes internal to ConfigureDialog // see configuredialog.cpp for details. // my header: #include "configuredialog_p.h" // other KMail headers: #include "settings/kmailsettings.h" // other KDE headers: #include // Qt headers: #include #include // Other headers: ConfigModuleWithTabs::ConfigModuleWithTabs(QWidget *parent) : ConfigModule(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); - vlay->setMargin(0); + vlay->setContentsMargins(0, 0, 0, 0); mTabWidget = new QTabWidget(this); vlay->addWidget(mTabWidget); } void ConfigModuleWithTabs::addTab(ConfigModuleTab *tab, const QString &title) { mTabWidget->addTab(tab, title); connect(tab, SIGNAL(changed(bool)), this, SIGNAL(changed(bool))); } void ConfigModuleWithTabs::showEvent(QShowEvent *event) { mWasInitialized = true; ConfigModule::showEvent(event); } void ConfigModuleWithTabs::load() { const int numberOfTab = mTabWidget->count(); for (int i = 0; i < numberOfTab; ++i) { ConfigModuleTab *tab = qobject_cast(mTabWidget->widget(i)); if (tab) { tab->load(); } } KCModule::load(); } void ConfigModuleWithTabs::save() { if (mWasInitialized) { KCModule::save(); const int numberOfTab = mTabWidget->count(); for (int i = 0; i < numberOfTab; ++i) { ConfigModuleTab *tab = qobject_cast(mTabWidget->widget(i)); if (tab) { tab->save(); } } } } void ConfigModuleWithTabs::defaults() { ConfigModuleTab *tab = qobject_cast(mTabWidget->currentWidget()); if (tab) { tab->defaults(); } KCModule::defaults(); } void ConfigModuleTab::load() { mEmitChanges = false; doLoadFromGlobalSettings(); doLoadOther(); mEmitChanges = true; } void ConfigModuleTab::defaults() { // reset settings which are available via GlobalSettings to their defaults // (stolen from KConfigDialogManager::updateWidgetsDefault()) const bool bUseDefaults = KMailSettings::self()->useDefaults(true); doLoadFromGlobalSettings(); KMailSettings::self()->useDefaults(bUseDefaults); // reset other settings to default values doResetToDefaultsOther(); } void ConfigModuleTab::slotEmitChanged() { if (mEmitChanges) { Q_EMIT changed(true); } } diff --git a/src/dialog/archivefolderdialog.cpp b/src/dialog/archivefolderdialog.cpp index 88ad7802d..50c0318ac 100644 --- a/src/dialog/archivefolderdialog.cpp +++ b/src/dialog/archivefolderdialog.cpp @@ -1,228 +1,228 @@ /* Copyright 2009 Klarälvdalens Datakonsult AB 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #include "archivefolderdialog.h" #include "MailCommon/BackupJob" #include "kmkernel.h" #include "kmmainwidget.h" #include "MailCommon/FolderRequester" #include "MessageViewer/MessageViewerUtil" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KMail; using namespace MailCommon; QString ArchiveFolderDialog::standardArchivePath(const QString &folderName) { QString currentPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QDir dir(currentPath); if (!dir.exists()) { currentPath = QDir::homePath(); } return currentPath + QLatin1Char('/') +i18nc("Start of the filename for a mail archive file", "Archive") + QLatin1Char('_') + folderName + QLatin1Char('_') + QDate::currentDate().toString(Qt::ISODate) + QLatin1String(".tar.bz2"); } ArchiveFolderDialog::ArchiveFolderDialog(QWidget *parent) : QDialog(parent) , mParentWidget(parent) { setObjectName(QStringLiteral("archive_folder_dialog")); setWindowTitle(i18nc("@title:window for archiving a folder", "Archive Folder")); QVBoxLayout *topLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ArchiveFolderDialog::slotAccepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &ArchiveFolderDialog::reject); mOkButton->setDefault(true); mOkButton->setText(i18nc("@action", "Archive")); setModal(true); QWidget *mainWidget = new QWidget(this); topLayout->addWidget(mainWidget); topLayout->addWidget(buttonBox); QGridLayout *mainLayout = new QGridLayout(mainWidget); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); int row = 0; // TODO: Explaination label QLabel *folderLabel = new QLabel(i18n("&Folder:"), mainWidget); mainLayout->addWidget(folderLabel, row, 0); mFolderRequester = new FolderRequester(mainWidget); mFolderRequester->setMustBeReadWrite(false); mFolderRequester->setNotAllowToCreateNewFolder(true); connect(mFolderRequester, &FolderRequester::folderChanged, this, &ArchiveFolderDialog::slotFolderChanged); folderLabel->setBuddy(mFolderRequester); mainLayout->addWidget(mFolderRequester, row, 1); row++; QLabel *formatLabel = new QLabel(i18n("F&ormat:"), mainWidget); mainLayout->addWidget(formatLabel, row, 0); mFormatComboBox = new KComboBox(mainWidget); formatLabel->setBuddy(mFormatComboBox); // These combobox values have to stay in sync with the ArchiveType enum from BackupJob! mFormatComboBox->addItem(i18n("Compressed Zip Archive (.zip)")); mFormatComboBox->addItem(i18n("Uncompressed Archive (.tar)")); mFormatComboBox->addItem(i18n("BZ2-Compressed Tar Archive (.tar.bz2)")); mFormatComboBox->addItem(i18n("GZ-Compressed Tar Archive (.tar.gz)")); mFormatComboBox->setCurrentIndex(2); connect(mFormatComboBox, QOverload::of(&KComboBox::activated), this, &ArchiveFolderDialog::slotFixFileExtension); mainLayout->addWidget(mFormatComboBox, row, 1); row++; QLabel *fileNameLabel = new QLabel(i18n("&Archive File:"), mainWidget); mainLayout->addWidget(fileNameLabel, row, 0); mUrlRequester = new KUrlRequester(mainWidget); mUrlRequester->setMode(KFile::LocalOnly | KFile::File); mUrlRequester->setFilter(QStringLiteral("*.tar *.zip *.tar.gz *.tar.bz2")); fileNameLabel->setBuddy(mUrlRequester); connect(mUrlRequester, &KUrlRequester::urlSelected, this, &ArchiveFolderDialog::slotFixFileExtension); connect(mUrlRequester, &KUrlRequester::textChanged, this, &ArchiveFolderDialog::slotUrlChanged); mainLayout->addWidget(mUrlRequester, row, 1); row++; // TODO: Make this appear more dangerous! mDeleteCheckBox = new QCheckBox(i18n("&Delete folder and subfolders after completion"), mainWidget); mainLayout->addWidget(mDeleteCheckBox, row, 0, 1, 2, Qt::AlignLeft); row++; mRecursiveCheckBox = new QCheckBox(i18n("Archive all subfolders"), mainWidget); connect(mRecursiveCheckBox, &QCheckBox::clicked, this, &ArchiveFolderDialog::slotRecursiveCheckboxClicked); mainLayout->addWidget(mRecursiveCheckBox, row, 0, 1, 2, Qt::AlignLeft); mRecursiveCheckBox->setChecked(true); row++; // TODO: what's this, tooltips // TODO: Warn that user should do mail check for online IMAP and possibly cached IMAP as well mainLayout->addWidget(new KSeparator(), row, 0, 1, 2); row++; mainLayout->setColumnStretch(1, 1); mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), row, 0); // Make it a bit bigger, else the folder requester cuts off the text too early resize(500, minimumSize().height()); } bool canRemoveFolder(const Akonadi::Collection &col) { const QSharedPointer folder = FolderSettings::forCollection(col, false); return folder && col.isValid() && !col.isVirtual() && (col.rights() & Akonadi::Collection::CanDeleteCollection) && !folder->isStructural() && !folder->isSystemFolder(); } void ArchiveFolderDialog::slotRecursiveCheckboxClicked() { slotFolderChanged(mFolderRequester->collection()); } void ArchiveFolderDialog::slotFolderChanged(const Akonadi::Collection &folder) { mDeleteCheckBox->setEnabled(allowToDeleteFolders(folder)); } bool ArchiveFolderDialog::allowToDeleteFolders(const Akonadi::Collection &folder) const { return canRemoveFolder(folder) && mRecursiveCheckBox->isChecked(); } void ArchiveFolderDialog::setFolder(const Akonadi::Collection &defaultCollection) { mFolderRequester->setCollection(defaultCollection); // TODO: what if the file already exists? mUrlRequester->setUrl(QUrl::fromLocalFile(standardArchivePath(defaultCollection.name()))); const QSharedPointer folder = FolderSettings::forCollection(defaultCollection, false); mDeleteCheckBox->setEnabled(allowToDeleteFolders(defaultCollection)); mOkButton->setEnabled(defaultCollection.isValid() && folder && !folder->isStructural()); } void ArchiveFolderDialog::slotAccepted() { if (!MessageViewer::Util::checkOverwrite(mUrlRequester->url(), this)) { return; } if (!mFolderRequester->hasCollection()) { KMessageBox::information(this, i18n("Please select the folder that should be archived."), i18n("No folder selected")); return; } MailCommon::BackupJob *backupJob = new MailCommon::BackupJob(mParentWidget); backupJob->setRootFolder(mFolderRequester->collection()); backupJob->setSaveLocation(mUrlRequester->url()); backupJob->setArchiveType(static_cast(mFormatComboBox->currentIndex())); backupJob->setDeleteFoldersAfterCompletion(mDeleteCheckBox->isEnabled() && mDeleteCheckBox->isChecked()); backupJob->setRecursive(mRecursiveCheckBox->isChecked()); backupJob->start(); accept(); } void ArchiveFolderDialog::slotFixFileExtension() { const int numExtensions = 4; // The extensions here are also sorted, like the enum order of BackupJob::ArchiveType const char *extensions[numExtensions] = { ".zip", ".tar", ".tar.bz2", ".tar.gz" }; QString fileName = mUrlRequester->url().path(); if (fileName.isEmpty()) { fileName = standardArchivePath(mFolderRequester->hasCollection() ? mFolderRequester->collection().name() : QString()); } QMimeDatabase db; const QString extension = db.suffixForFileName(fileName); if (!extension.isEmpty()) { fileName.truncate(fileName.length() - extension.length() - 1); } // Now, we've got a filename without an extension, simply append the correct one fileName += QLatin1String(extensions[mFormatComboBox->currentIndex()]); mUrlRequester->setUrl(QUrl::fromLocalFile(fileName)); } void ArchiveFolderDialog::slotUrlChanged(const QString &url) { mOkButton->setEnabled(!url.isEmpty()); } diff --git a/src/editor/attachment/attachmentview.cpp b/src/editor/attachment/attachmentview.cpp index 1feafcdff..fd881fd47 100644 --- a/src/editor/attachment/attachmentview.cpp +++ b/src/editor/attachment/attachmentview.cpp @@ -1,218 +1,218 @@ /* * This file is part of KMail. * Copyright (c) 2011-2019 Laurent Montel * * Copyright (c) 2009 Constantin Berzan * * Parts based on KMail code by: * Copyright (c) 2003 Ingo Kloecker * Copyright (c) 2007 Thomas McGuire * * 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 "attachmentview.h" #include "kmkernel.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using MessageCore::AttachmentPart; using namespace KMail; AttachmentView::AttachmentView(MessageComposer::AttachmentModel *model, QWidget *parent) : QTreeView(parent) , grp(KMKernel::self()->config()->group("AttachmentView")) { mWidget = new QWidget(); QHBoxLayout *lay = new QHBoxLayout(mWidget); - lay->setMargin(0); + lay->setContentsMargins(0, 0, 0, 0); mToolButton = new QToolButton; connect(mToolButton, &QAbstractButton::toggled, this, &AttachmentView::slotShowHideAttchementList); mToolButton->setIcon(QIcon::fromTheme(QStringLiteral("mail-attachment"))); mToolButton->setAutoRaise(true); mToolButton->setCheckable(true); lay->addWidget(mToolButton); mInfoAttachment = new QLabel; - mInfoAttachment->setMargin(0); + mInfoAttachment->setContentsMargins(0, 0, 0, 0); mInfoAttachment->setTextFormat(Qt::PlainText); lay->addWidget(mInfoAttachment); mModel = model; connect(model, &MessageComposer::AttachmentModel::encryptEnabled, this, &AttachmentView::setEncryptEnabled); connect(model, &MessageComposer::AttachmentModel::signEnabled, this, &AttachmentView::setSignEnabled); QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(this); sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); sortModel->setSourceModel(model); setModel(sortModel); connect(model, &MessageComposer::AttachmentModel::rowsInserted, this, &AttachmentView::hideIfEmpty); connect(model, &MessageComposer::AttachmentModel::rowsRemoved, this, &AttachmentView::hideIfEmpty); connect(model, &MessageComposer::AttachmentModel::rowsRemoved, this, &AttachmentView::selectNewAttachment); connect(model, &MessageComposer::AttachmentModel::dataChanged, this, &AttachmentView::updateAttachmentLabel); setRootIsDecorated(false); setUniformRowHeights(true); setSelectionMode(QAbstractItemView::ExtendedSelection); setDragDropMode(QAbstractItemView::DragDrop); setEditTriggers(QAbstractItemView::EditKeyPressed); setDropIndicatorShown(false); setSortingEnabled(true); header()->setSectionResizeMode(QHeaderView::Interactive); header()->setStretchLastSection(false); restoreHeaderState(); setColumnWidth(0, 200); } AttachmentView::~AttachmentView() { saveHeaderState(); } void AttachmentView::restoreHeaderState() { header()->restoreState(grp.readEntry("State", QByteArray())); } void AttachmentView::saveHeaderState() { grp.writeEntry("State", header()->saveState()); grp.sync(); } void AttachmentView::contextMenuEvent(QContextMenuEvent *event) { Q_UNUSED(event); Q_EMIT contextMenuRequested(); } void AttachmentView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Delete) { // Indexes are based on row numbers, and row numbers change when items are deleted. // Therefore, first we need to make a list of AttachmentParts to delete. AttachmentPart::List toRemove; const QModelIndexList selectedIndexes = selectionModel()->selectedRows(); toRemove.reserve(selectedIndexes.count()); for (const QModelIndex &index : selectedIndexes) { AttachmentPart::Ptr part = model()->data( index, MessageComposer::AttachmentModel::AttachmentPartRole).value(); toRemove.append(part); } for (const AttachmentPart::Ptr &part : qAsConst(toRemove)) { mModel->removeAttachment(part); } } else { QTreeView::keyPressEvent(event); } } void AttachmentView::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == this) { // Ignore drags from ourselves. event->ignore(); } else { QTreeView::dragEnterEvent(event); } } void AttachmentView::setEncryptEnabled(bool enabled) { setColumnHidden(MessageComposer::AttachmentModel::EncryptColumn, !enabled); } void AttachmentView::setSignEnabled(bool enabled) { setColumnHidden(MessageComposer::AttachmentModel::SignColumn, !enabled); } void AttachmentView::hideIfEmpty() { const bool needToShowIt = (model()->rowCount() > 0); setVisible(needToShowIt); mToolButton->setChecked(needToShowIt); widget()->setVisible(needToShowIt); if (needToShowIt) { updateAttachmentLabel(); } else { mInfoAttachment->clear(); } Q_EMIT modified(true); } void AttachmentView::updateAttachmentLabel() { const MessageCore::AttachmentPart::List list = mModel->attachments(); qint64 size = 0; for (const MessageCore::AttachmentPart::Ptr &part : list) { size += part->size(); } mInfoAttachment->setText(i18np("1 attachment (%2)", "%1 attachments (%2)", model()->rowCount(), KFormat().formatByteSize(qMax(0LL, size)))); } void AttachmentView::selectNewAttachment() { if (selectionModel()->selectedRows().isEmpty()) { selectionModel()->select(selectionModel()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows); } } void AttachmentView::startDrag(Qt::DropActions supportedActions) { Q_UNUSED(supportedActions); const QModelIndexList selection = selectionModel()->selectedRows(); if (!selection.isEmpty()) { QMimeData *mimeData = model()->mimeData(selection); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); } } QWidget *AttachmentView::widget() const { return mWidget; } void AttachmentView::slotShowHideAttchementList(bool show) { setVisible(show); if (show) { mToolButton->setToolTip(i18n("Hide attachment list")); } else { mToolButton->setToolTip(i18n("Show attachment list")); } } diff --git a/src/editor/kmcomposerwin.cpp b/src/editor/kmcomposerwin.cpp index b3b0d6df2..21af0845b 100644 --- a/src/editor/kmcomposerwin.cpp +++ b/src/editor/kmcomposerwin.cpp @@ -1,3624 +1,3624 @@ /* * This file is part of KMail. * Copyright (c) 2011-2019 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/plugininterface/kmailplugingrammareditormanagerinterface.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 #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); mPluginEditorGrammarManagerInterface = new KMailPluginGrammarEditorManagerInterface(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->setContentsMargins(0, 0, 0, 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")); 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); 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(this); mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator()); QVBoxLayout *vbox = new QVBoxLayout(editorAndCryptoStateIndicators); - vbox->setMargin(0); + vbox->setContentsMargins(0, 0, 0, 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(this); mCustomToolsWidget->initializeView(actionCollection(), PimCommon::CustomToolsPluginManager::self()->pluginsList()); 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->setParentWidget(this); mPluginEditorInitManagerInterface->setRichTextEditor(composerEditorNg); mPluginEditorConvertTextManagerInterface->setParentWidget(this); mPluginEditorConvertTextManagerInterface->setActionCollection(actionCollection()); mPluginEditorConvertTextManagerInterface->setRichTextEditor(composerEditorNg); mPluginEditorGrammarManagerInterface->setParentWidget(this); mPluginEditorGrammarManagerInterface->setActionCollection(actionCollection()); mPluginEditorGrammarManagerInterface->setRichTextEditor(composerEditorNg); mPluginEditorGrammarManagerInterface->setCustomToolsWidget(mCustomToolsWidget); 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()); 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); 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() { 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->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 == 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_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_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; mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2); ++row; 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()); 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(); //Readd ? 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() { 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); 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(); mPluginEditorGrammarManagerInterface->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); } } const QList customToolsWidgetActionList = mCustomToolsWidget->actionList(); const QString actionlistname = QStringLiteral("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(MessageComposer::PluginActionType::Tools); for (KToggleAction *act : customToolsWidgetActionList) { QList lst; lst << act; 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); } const QList statusbarWidgetListConverter = mPluginEditorConvertTextManagerInterface->statusBarWidgetList(); for (int i = 0; i < statusbarWidgetListConverter.count(); ++i) { statusBar()->addPermanentWidget(statusbarWidgetListConverter.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() { QFontMetrics fm(mBodyFont); mComposerBase->editor()->setTabStopDistance(fm.boundingRect(QLatin1Char(' ')).width() * 8); 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()); } void KMComposerWin::slotInvalidIdentity() { mIncorrectIdentityFolderWarning->identityInvalid(); } void KMComposerWin::slotFccIsInvalid() { mIncorrectIdentityFolderWarning->fccIsInvalid(); } void KMComposerWin::setCurrentTransport(int transportId) { if (!mComposerBase->transportComboBox()->setCurrentTransport(transportId)) { mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); } } 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()*/); } // 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()); } 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() || 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); 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) { const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment(); if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) { return; } //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; } } 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->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(); } enableDisablePluginActions(mode == MessageComposer::RichTextComposerNg::Rich); } void KMComposerWin::enableDisablePluginActions(bool richText) { mPluginEditorConvertTextManagerInterface->enableDisablePluginActions(richText); } 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; } // 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); 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; } MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus KMComposerWin::convertPlainText(MessageComposer::TextPart *textPart) { return mPluginEditorConvertTextManagerInterface->convertTextToFormat(textPart); } 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) + mPluginEditorConvertTextManagerInterface->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/editor/potentialphishingemail/potentialphishingdetailwidget.cpp b/src/editor/potentialphishingemail/potentialphishingdetailwidget.cpp index 6ac97df54..66dd5534c 100644 --- a/src/editor/potentialphishingemail/potentialphishingdetailwidget.cpp +++ b/src/editor/potentialphishingemail/potentialphishingdetailwidget.cpp @@ -1,80 +1,80 @@ /* Copyright (c) 2015-2019 Montel Laurent 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 "potentialphishingdetailwidget.h" #include #include #include #include #include #include PotentialPhishingDetailWidget::PotentialPhishingDetailWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); QLabel *lab = new QLabel(i18n("Select email to put in whitelist:"), this); lab->setObjectName(QStringLiteral("label")); mainLayout->addWidget(lab); mListWidget = new QListWidget(this); mListWidget->setObjectName(QStringLiteral("list_widget")); mainLayout->addWidget(mListWidget); } PotentialPhishingDetailWidget::~PotentialPhishingDetailWidget() { } void PotentialPhishingDetailWidget::fillList(const QStringList &lst) { mListWidget->clear(); QStringList emailsAdded; for (const QString &mail : lst) { if (!emailsAdded.contains(mail)) { QListWidgetItem *item = new QListWidgetItem(mListWidget); item->setCheckState(Qt::Unchecked); item->setText(mail); emailsAdded << mail; } } } void PotentialPhishingDetailWidget::save() { KConfigGroup group(KSharedConfig::openConfig(), "PotentialPhishing"); QStringList potentialPhishing = group.readEntry("whiteList", QStringList()); bool emailsAdded = false; const int numberOfItem(mListWidget->count()); for (int i = 0; i < numberOfItem; ++i) { QListWidgetItem *item = mListWidget->item(i); if (item->checkState() == Qt::Checked) { const QString email = item->text(); if (!potentialPhishing.contains(email)) { potentialPhishing << email; emailsAdded = true; } } } if (emailsAdded) { group.writeEntry("whiteList", potentialPhishing); } } diff --git a/src/editor/widgets/cryptostateindicatorwidget.cpp b/src/editor/widgets/cryptostateindicatorwidget.cpp index e90504f77..d32d44095 100644 --- a/src/editor/widgets/cryptostateindicatorwidget.cpp +++ b/src/editor/widgets/cryptostateindicatorwidget.cpp @@ -1,99 +1,99 @@ /* Copyright (C) 2014-2019 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 "cryptostateindicatorwidget.h" #include "MessageCore/MessageCoreUtil" #include #include #include #include CryptoStateIndicatorWidget::CryptoStateIndicatorWidget(QWidget *parent) : QWidget(parent) { QHBoxLayout *hbox = new QHBoxLayout(this); - hbox->setMargin(0); + hbox->setContentsMargins(0, 0, 0, 0); mSignatureStateIndicator = new QLabel(this); mSignatureStateIndicator->setAlignment(Qt::AlignHCenter); mSignatureStateIndicator->setTextFormat(Qt::PlainText); hbox->addWidget(mSignatureStateIndicator); mSignatureStateIndicator->setObjectName(QStringLiteral("signatureindicator")); QPalette p(mSignatureStateIndicator->palette()); p.setColor(QPalette::Window, MessageCore::ColorUtil::self()->pgpSignedTrustedMessageColor()); p.setColor(QPalette::Text, MessageCore::ColorUtil::self()->pgpSignedTrustedTextColor()); mSignatureStateIndicator->setPalette(p); mSignatureStateIndicator->setAutoFillBackground(true); mEncryptionStateIndicator = new QLabel(this); mEncryptionStateIndicator->setAlignment(Qt::AlignHCenter); mEncryptionStateIndicator->setTextFormat(Qt::PlainText); hbox->addWidget(mEncryptionStateIndicator); p = mEncryptionStateIndicator->palette(); p.setColor(QPalette::Window, MessageCore::ColorUtil::self()->pgpEncryptedMessageColor()); p.setColor(QPalette::Text, MessageCore::ColorUtil::self()->pgpEncryptedTextColor()); mEncryptionStateIndicator->setPalette(p); mEncryptionStateIndicator->setAutoFillBackground(true); mEncryptionStateIndicator->setObjectName(QStringLiteral("encryptionindicator")); hide(); } CryptoStateIndicatorWidget::~CryptoStateIndicatorWidget() { } void CryptoStateIndicatorWidget::setShowAlwaysIndicator(bool status) { if (mShowAlwaysIndicator != status) { mShowAlwaysIndicator = status; updateShowAlwaysIndicator(); } } void CryptoStateIndicatorWidget::updateShowAlwaysIndicator() { if (mShowAlwaysIndicator) { mSignatureStateIndicator->setVisible(mIsSign); mEncryptionStateIndicator->setVisible(mIsEncrypted); if (mIsSign || mIsEncrypted) { show(); } else { hide(); } } else { mSignatureStateIndicator->setVisible(false); mEncryptionStateIndicator->setVisible(false); hide(); } } void CryptoStateIndicatorWidget::updateSignatureAndEncrypionStateIndicators(bool isSign, bool isEncrypted) { mIsEncrypted = isEncrypted; mIsSign = isSign; mSignatureStateIndicator->setText(isSign ? i18n("Message will be signed") : i18n("Message will not be signed")); mEncryptionStateIndicator->setText(isEncrypted ? i18n("Message will be encrypted") : i18n("Message will not be encrypted")); updateShowAlwaysIndicator(); } diff --git a/src/identity/identityaddvcarddialog.cpp b/src/identity/identityaddvcarddialog.cpp index ffe82ac54..7f62b67d4 100644 --- a/src/identity/identityaddvcarddialog.cpp +++ b/src/identity/identityaddvcarddialog.cpp @@ -1,133 +1,133 @@ /* Copyright (c) 2012-2019 Montel Laurent This program 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. 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 "identityaddvcarddialog.h" #include #include #include #include #include #include #include #include #include #include IdentityAddVcardDialog::IdentityAddVcardDialog(const QStringList &shadowIdentities, QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Create own vCard")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QVBoxLayout *mainLayout = new QVBoxLayout(this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &IdentityAddVcardDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &IdentityAddVcardDialog::reject); setModal(true); QWidget *mainWidget = new QWidget(this); mainLayout->addWidget(mainWidget); mainLayout->addWidget(buttonBox); QVBoxLayout *vlay = new QVBoxLayout(mainWidget); - vlay->setMargin(0); + vlay->setContentsMargins(0, 0, 0, 0); mButtonGroup = new QButtonGroup(this); mButtonGroup->setObjectName(QStringLiteral("buttongroup")); // row 1: radio button QRadioButton *radio = new QRadioButton(i18n("&With empty fields"), this); radio->setChecked(true); vlay->addWidget(radio); mButtonGroup->addButton(radio, static_cast(Empty)); // row 2: radio button QRadioButton *fromExistingVCard = new QRadioButton(i18n("&From existing vCard"), this); vlay->addWidget(fromExistingVCard); mButtonGroup->addButton(fromExistingVCard, static_cast(FromExistingVCard)); // row 3: KUrlRequester QHBoxLayout *hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mVCardPath = new KUrlRequester; mVCardPath->setObjectName(QStringLiteral("kurlrequester_vcardpath")); mVCardPath->setMimeTypeFilters({QStringLiteral("text/vcard"), QStringLiteral("all/allfiles")}); mVCardPath->setMode(KFile::LocalOnly | KFile::File); QLabel *label = new QLabel(i18n("&vCard path:"), this); label->setBuddy(mVCardPath); label->setEnabled(false); mVCardPath->setEnabled(false); hlay->addWidget(label); hlay->addWidget(mVCardPath); connect(fromExistingVCard, &QRadioButton::toggled, label, &QLabel::setEnabled); connect(fromExistingVCard, &QRadioButton::toggled, mVCardPath, &KUrlRequester::setEnabled); // row 4: radio button QRadioButton *duplicateExistingVCard = new QRadioButton(i18n("&Duplicate existing vCard"), this); vlay->addWidget(duplicateExistingVCard); mButtonGroup->addButton(duplicateExistingVCard, static_cast(ExistingEntry)); // row 5: combobox with existing identities and label hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mComboBox = new QComboBox(this); mComboBox->setObjectName(QStringLiteral("identity_combobox")); mComboBox->setEditable(false); mComboBox->addItems(shadowIdentities); mComboBox->setEnabled(false); label = new QLabel(i18n("&Existing identities:"), this); label->setBuddy(mComboBox); label->setEnabled(false); hlay->addWidget(label); hlay->addWidget(mComboBox, 1); vlay->addWidget(new KSeparator); vlay->addStretch(1); // spacer // enable/disable combobox and label depending on the third radio // button's state: connect(duplicateExistingVCard, &QRadioButton::toggled, label, &QLabel::setEnabled); connect(duplicateExistingVCard, &QRadioButton::toggled, mComboBox, &QComboBox::setEnabled); resize(350, 130); } IdentityAddVcardDialog::~IdentityAddVcardDialog() { } IdentityAddVcardDialog::DuplicateMode IdentityAddVcardDialog::duplicateMode() const { const int id = mButtonGroup->checkedId(); return static_cast(id); } QString IdentityAddVcardDialog::duplicateVcardFromIdentity() const { return mComboBox->currentText(); } QUrl IdentityAddVcardDialog::existingVCard() const { return mVCardPath->url(); } diff --git a/src/identity/identitydialog.cpp b/src/identity/identitydialog.cpp index b1912a39d..0a7bbddd5 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-2019 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); + vlay->setContentsMargins(0, 0, 0, 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 https://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 https://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, [](const Akonadi::Collection::List &cols) { 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/identity/identitylistview.cpp b/src/identity/identitylistview.cpp index 4ba2059dd..814c945e8 100644 --- a/src/identity/identitylistview.cpp +++ b/src/identity/identitylistview.cpp @@ -1,216 +1,216 @@ /* -*- c++ -*- identitylistview.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2002 Marc Mutz 2007 Mathias Soeken 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 "identitylistview.h" #include #include #ifndef KCM_KPIMIDENTITIES_STANDALONE #include "kmkernel.h" #endif #include "kmail_debug.h" #include #include #include #include #include using namespace KMail; // // // IdentityListViewItem // // IdentityListViewItem::IdentityListViewItem(IdentityListView *parent, const KIdentityManagement::Identity &ident) : QTreeWidgetItem(parent) , mUOID(ident.uoid()) { init(ident); } IdentityListViewItem::IdentityListViewItem(IdentityListView *parent, QTreeWidgetItem *after, const KIdentityManagement::Identity &ident) : QTreeWidgetItem(parent, after) , mUOID(ident.uoid()) { init(ident); } uint IdentityListViewItem::uoid() const { return mUOID; } KIdentityManagement::Identity &IdentityListViewItem::identity() const { KIdentityManagement::IdentityManager *im = qobject_cast(treeWidget())->identityManager(); Q_ASSERT(im); return im->modifyIdentityForUoid(mUOID); } void IdentityListViewItem::setIdentity(const KIdentityManagement::Identity &ident) { mUOID = ident.uoid(); init(ident); } void IdentityListViewItem::redisplay() { init(identity()); } void IdentityListViewItem::init(const KIdentityManagement::Identity &ident) { if (ident.isDefault()) { // Add "(Default)" to the end of the default identity's name: setText(0, i18nc("%1: identity name. Used in the config " "dialog, section Identity, to indicate the " "default identity", "%1 (Default)", ident.identityName())); QFont fontItem(font(0)); fontItem.setBold(true); setFont(0, fontItem); } else { QFont fontItem(font(0)); fontItem.setBold(false); setFont(0, fontItem); setText(0, ident.identityName()); } setText(1, ident.fullEmailAddr()); } // // // IdentityListView // // IdentityListView::IdentityListView(QWidget *parent) : QTreeWidget(parent) , mIdentityManager(nullptr) { #ifndef QT_NO_DRAGANDDROP setDragEnabled(true); setAcceptDrops(true); #endif setHeaderLabels(QStringList() << i18n("Identity Name") << i18n("Email Address")); setRootIsDecorated(false); header()->setSectionsMovable(false); header()->setSectionResizeMode(QHeaderView::ResizeToContents); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); setSelectionMode(SingleSelection); // ### Extended would be nicer... setColumnWidth(0, 175); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &IdentityListView::customContextMenuRequested, this, &IdentityListView::slotCustomContextMenuRequested); } void IdentityListView::editItem(QTreeWidgetItem *item, int column) { if (column == 0 && item) { IdentityListViewItem *lvItem = dynamic_cast(item); if (lvItem) { KIdentityManagement::Identity &ident = lvItem->identity(); if (ident.isDefault()) { lvItem->setText(0, ident.identityName()); } } Qt::ItemFlags oldFlags = item->flags(); item->setFlags(oldFlags | Qt::ItemIsEditable); QTreeWidget::editItem(item, 0); item->setFlags(oldFlags); } } void IdentityListView::commitData(QWidget *editor) { qCDebug(KMAIL_LOG) << "after editing"; if (!selectedItems().isEmpty()) { QLineEdit *edit = qobject_cast(editor); if (edit) { IdentityListViewItem *item = dynamic_cast(selectedItems().at(0)); const QString text = edit->text(); Q_EMIT rename(item, text); } } } void IdentityListView::slotCustomContextMenuRequested(const QPoint &pos) { QTreeWidgetItem *item = itemAt(pos); if (item) { IdentityListViewItem *lvItem = dynamic_cast(item); if (lvItem) { Q_EMIT contextMenu(lvItem, viewport()->mapToGlobal(pos)); } } else { Q_EMIT contextMenu(nullptr, viewport()->mapToGlobal(pos)); } } #ifndef QT_NO_DRAGANDDROP void IdentityListView::startDrag(Qt::DropActions /*supportedActions*/) { IdentityListViewItem *item = dynamic_cast(currentItem()); if (!item) { return; } QDrag *drag = new QDrag(viewport()); QMimeData *md = new QMimeData; drag->setMimeData(md); item->identity().populateMimeData(md); drag->setPixmap(QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(16, 16)); - drag->start(); + drag->exec(Qt::CopyAction); } #endif KIdentityManagement::IdentityManager *IdentityListView::identityManager() const { Q_ASSERT(mIdentityManager); return mIdentityManager; } void IdentityListView::setIdentityManager(KIdentityManagement::IdentityManager *im) { mIdentityManager = im; } diff --git a/src/identity/newidentitydialog.cpp b/src/identity/newidentitydialog.cpp index b8b3f74e3..8f8c882c3 100644 --- a/src/identity/newidentitydialog.cpp +++ b/src/identity/newidentitydialog.cpp @@ -1,161 +1,161 @@ /* * kmail: KDE mail client * Copyright (C) 2000 Espen Sand, espen@kde.org * Copyright (C) 2001-2003 Marc Mutz, mutz@kde.org * Contains code segments and ideas from earlier kmail dialog code. * Copyright (C) 2010 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 "newidentitydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KMail; NewIdentityDialog::NewIdentityDialog(KIdentityManagement::IdentityManager *manager, QWidget *parent) : QDialog(parent) , mIdentityManager(manager) { setWindowTitle(i18n("New Identity")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help, this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &NewIdentityDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &NewIdentityDialog::reject); connect(buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &NewIdentityDialog::slotHelp); QWidget *page = new QWidget(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); QVBoxLayout *vlay = new QVBoxLayout(page); - vlay->setMargin(0); + vlay->setContentsMargins(0, 0, 0, 0); // row 0: line edit with label QHBoxLayout *hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mLineEdit = new KLineEdit(page); mLineEdit->setFocus(); mLineEdit->setClearButtonEnabled(true); QLabel *l = new QLabel(i18n("&New identity:"), page); l->setBuddy(mLineEdit); hlay->addWidget(l); hlay->addWidget(mLineEdit, 1); connect(mLineEdit, &KLineEdit::textChanged, this, &NewIdentityDialog::slotEnableOK); mButtonGroup = new QButtonGroup(page); // row 1: radio button QRadioButton *radio = new QRadioButton(i18n("&With empty fields"), page); radio->setChecked(true); vlay->addWidget(radio); mButtonGroup->addButton(radio, static_cast(Empty)); // row 2: radio button radio = new QRadioButton(i18n("&Use System Settings values"), page); vlay->addWidget(radio); mButtonGroup->addButton(radio, static_cast(ControlCenter)); // row 3: radio button radio = new QRadioButton(i18n("&Duplicate existing identity"), page); vlay->addWidget(radio); mButtonGroup->addButton(radio, static_cast(ExistingEntry)); // row 4: combobox with existing identities and label hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mComboBox = new KComboBox(page); mComboBox->setEditable(false); mComboBox->addItems(manager->shadowIdentities()); mComboBox->setEnabled(false); QLabel *label = new QLabel(i18n("&Existing identities:"), page); label->setBuddy(mComboBox); label->setEnabled(false); hlay->addWidget(label); hlay->addWidget(mComboBox, 1); vlay->addWidget(new KSeparator); vlay->addStretch(1); // spacer // enable/disable combobox and label depending on the third radio // button's state: connect(radio, &QRadioButton::toggled, label, &QLabel::setEnabled); connect(radio, &QRadioButton::toggled, mComboBox, &KComboBox::setEnabled); mOkButton->setEnabled(false); // since line edit is empty resize(400, 180); } void NewIdentityDialog::slotHelp() { PimCommon::Util::invokeHelp(QStringLiteral("kmail2/configure-identity.html"), QStringLiteral("configure-identity-newidentitydialog")); } NewIdentityDialog::DuplicateMode NewIdentityDialog::duplicateMode() const { const int id = mButtonGroup->checkedId(); assert(id == static_cast(Empty) || id == static_cast(ControlCenter) || id == static_cast(ExistingEntry)); return static_cast(id); } void NewIdentityDialog::slotEnableOK(const QString &proposedIdentityName) { // OK button is disabled if const QString name = proposedIdentityName.trimmed(); // name isn't empty if (name.isEmpty()) { mOkButton->setEnabled(false); return; } // or name doesn't yet exist. if (!mIdentityManager->isUnique(name)) { mOkButton->setEnabled(false); return; } mOkButton->setEnabled(true); } QString NewIdentityDialog::identityName() const { return mLineEdit->text(); } QString NewIdentityDialog::duplicateIdentity() const { return mComboBox->currentText(); } diff --git a/src/identity/xfaceconfigurator.cpp b/src/identity/xfaceconfigurator.cpp index 498482ce2..c2d0aa6b2 100644 --- a/src/identity/xfaceconfigurator.cpp +++ b/src/identity/xfaceconfigurator.cpp @@ -1,289 +1,289 @@ /* This file is part of KMail. Copyright (c) 2004 Jakob Schr�er 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. 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 "xfaceconfigurator.h" #include #include #include #include "kpimtextedit/plaintexteditor.h" #include "kpimtextedit/plaintexteditorwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KContacts; using namespace KIO; using namespace KMail; using namespace MessageViewer; using namespace KMail; XFaceConfigurator::XFaceConfigurator(QWidget *parent) : QWidget(parent) { QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setObjectName(QStringLiteral("main layout")); QHBoxLayout *hlay = new QHBoxLayout(); vlay->addLayout(hlay); // "enable X-Face" checkbox: mEnableCheck = new QCheckBox(i18n("&Send picture with every message"), this); mEnableCheck->setWhatsThis( i18n("Check this box if you want KMail to add a so-called X-Face header to messages " "written with this identity. An X-Face is a small (48x48 pixels) black and " "white image that some mail clients are able to display.")); hlay->addWidget(mEnableCheck, Qt::AlignLeft | Qt::AlignVCenter); mXFaceLabel = new QLabel(this); mXFaceLabel->setWhatsThis( i18n("This is a preview of the picture selected/entered below.")); mXFaceLabel->setFixedSize(48, 48); mXFaceLabel->setFrameShape(QFrame::Box); hlay->addWidget(mXFaceLabel); // label1 = new QLabel( "X-Face:", this ); // vlay->addWidget( label1 ); // "obtain X-Face from" combo and label: hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); KComboBox *sourceCombo = new KComboBox(this); sourceCombo->setEditable(false); sourceCombo->setWhatsThis( i18n("Click on the widgets below to obtain help on the input methods.")); sourceCombo->setEnabled(false); // since !mEnableCheck->isChecked() sourceCombo->addItems(QStringList() << i18nc("continuation of \"obtain picture from\"", "External Source") << i18nc("continuation of \"obtain picture from\"", "Input Field Below")); QLabel *label = new QLabel(i18n("Obtain pic&ture from:"), this); label->setBuddy(sourceCombo); label->setEnabled(false); // since !mEnableCheck->isChecked() hlay->addWidget(label); hlay->addWidget(sourceCombo, 1); // widget stack that is controlled by the source combo: QStackedWidget *widgetStack = new QStackedWidget(this); widgetStack->setEnabled(false); // since !mEnableCheck->isChecked() vlay->addWidget(widgetStack, 1); connect(sourceCombo, QOverload::of(&KComboBox::highlighted), widgetStack, &QStackedWidget::setCurrentIndex); connect(sourceCombo, QOverload::of(&KComboBox::activated), widgetStack, &QStackedWidget::setCurrentIndex); connect(mEnableCheck, &QCheckBox::toggled, sourceCombo, &KComboBox::setEnabled); connect(mEnableCheck, &QCheckBox::toggled, widgetStack, &QStackedWidget::setEnabled); connect(mEnableCheck, &QCheckBox::toggled, label, &QLabel::setEnabled); // The focus might be still in the widget that is disabled connect(mEnableCheck, &QAbstractButton::clicked, mEnableCheck, QOverload<>::of(&QWidget::setFocus)); int pageno = 0; // page 0: create X-Face from image file or address book entry QWidget *page = new QWidget(widgetStack); widgetStack->insertWidget(pageno, page); // force sequential numbers (play safe) QVBoxLayout *page_vlay = new QVBoxLayout(page); - page_vlay->setMargin(0); + page_vlay->setContentsMargins(0, 0, 0, 0); hlay = new QHBoxLayout(); // inherits spacing ??? FIXME really? page_vlay->addLayout(hlay); QPushButton *mFromFileBtn = new QPushButton(i18n("Select File..."), page); mFromFileBtn->setWhatsThis( i18n("Use this to select an image file to create the picture from. " "The image should be of high contrast and nearly quadratic shape. " "A light background helps improve the result.")); mFromFileBtn->setAutoDefault(false); page_vlay->addWidget(mFromFileBtn, 1); connect(mFromFileBtn, &QPushButton::released, this, &XFaceConfigurator::slotSelectFile); QPushButton *mFromAddrbkBtn = new QPushButton(i18n("Set From Address Book"), page); mFromAddrbkBtn->setWhatsThis( i18n("You can use a scaled-down version of the picture " "you have set in your address book entry.")); mFromAddrbkBtn->setAutoDefault(false); page_vlay->addWidget(mFromAddrbkBtn, 1); connect(mFromAddrbkBtn, &QPushButton::released, this, &XFaceConfigurator::slotSelectFromAddressbook); QLabel *label1 = new QLabel(i18n("KMail can send a small (48x48 pixels), low-quality, " "monochrome picture with every message. " "For example, this could be a picture of you or a glyph. " "It is shown in the recipient's mail client (if supported)."), page); label1->setAlignment(Qt::AlignVCenter); label1->setWordWrap(true); page_vlay->addWidget(label1); page_vlay->addStretch(); widgetStack->setCurrentIndex(0); // since sourceCombo->currentItem() == 0 // page 1: input field for direct entering ++pageno; page = new QWidget(widgetStack); widgetStack->insertWidget(pageno, page); page_vlay = new QVBoxLayout(page); - page_vlay->setMargin(0); + page_vlay->setContentsMargins(0, 0, 0, 0); mTextEdit = new KPIMTextEdit::PlainTextEditorWidget(page); mTextEdit->editor()->setSpellCheckingSupport(false); page_vlay->addWidget(mTextEdit); mTextEdit->editor()->setWhatsThis(i18n("Use this field to enter an arbitrary X-Face string.")); mTextEdit->editor()->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); mTextEdit->editor()->setWordWrapMode(QTextOption::WrapAnywhere); mTextEdit->editor()->setSearchSupport(false); QLabel *label2 = new QLabel(i18n("Examples are available at " "https://ace.home.xs4all.nl/X-Faces/."), page); label2->setOpenExternalLinks(true); label2->setTextInteractionFlags(Qt::TextBrowserInteraction); page_vlay->addWidget(label2); connect(mTextEdit->editor(), &KPIMTextEdit::PlainTextEditor::textChanged, this, &XFaceConfigurator::slotUpdateXFace); } XFaceConfigurator::~XFaceConfigurator() { } bool XFaceConfigurator::isXFaceEnabled() const { return mEnableCheck->isChecked(); } void XFaceConfigurator::setXFaceEnabled(bool enable) { mEnableCheck->setChecked(enable); } QString XFaceConfigurator::xface() const { return mTextEdit->editor()->toPlainText(); } void XFaceConfigurator::setXFace(const QString &text) { mTextEdit->editor()->setPlainText(text); } void XFaceConfigurator::setXfaceFromFile(const QUrl &url) { auto job = KIO::storedGet(url); KJobWidgets::setWindow(job, this); if (job->exec()) { KXFace xf; mTextEdit->editor()->setPlainText(xf.fromImage(QImage::fromData(job->data()))); } else { KMessageBox::error(this, job->errorString()); } } void XFaceConfigurator::slotSelectFile() { QString filter; const QList supportedImage = QImageReader::supportedImageFormats(); for (const QByteArray &ba : supportedImage) { if (!filter.isEmpty()) { filter += QLatin1Char(' '); } filter += QLatin1String("*.") + QString::fromLatin1(ba); } filter = QStringLiteral("%1 (%2)").arg(i18n("Image"), filter); const QUrl url = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), filter); if (!url.isEmpty()) { setXfaceFromFile(url); } } void XFaceConfigurator::slotSelectFromAddressbook() { using namespace KIdentityManagement; IdentityManager manager(true); const Identity defaultIdentity = manager.defaultIdentity(); const QString email = defaultIdentity.primaryEmailAddress().toLower(); Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(this); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email, Akonadi::ContactSearchJob::ExactMatch); connect(job, &Akonadi::ContactSearchJob::result, this, &XFaceConfigurator::slotDelayedSelectFromAddressbook); } void XFaceConfigurator::slotDelayedSelectFromAddressbook(KJob *job) { const Akonadi::ContactSearchJob *searchJob = qobject_cast(job); if (searchJob->contacts().isEmpty()) { KMessageBox::information(this, i18n("You do not have your own contact defined in the address book."), i18n("No Picture")); return; } const Addressee contact = searchJob->contacts().at(0); if (contact.photo().isIntern()) { const QImage photo = contact.photo().data(); if (!photo.isNull()) { KXFace xf; mTextEdit->editor()->setPlainText(xf.fromImage(photo)); } else { KMessageBox::information(this, i18n("No picture set for your address book entry."), i18n("No Picture")); } } else { const QUrl url(contact.photo().url()); if (!url.isEmpty()) { setXfaceFromFile(url); } else { KMessageBox::information(this, i18n("No picture set for your address book entry."), i18n("No Picture")); } } } void XFaceConfigurator::slotUpdateXFace() { QString str = mTextEdit->editor()->toPlainText(); if (!str.isEmpty()) { if (str.startsWith(QLatin1String("x-face:"), Qt::CaseInsensitive)) { str = str.remove(QStringLiteral("x-face:"), Qt::CaseInsensitive); mTextEdit->editor()->setPlainText(str); } KXFace xf; const QPixmap p = QPixmap::fromImage(xf.toImage(str)); mXFaceLabel->setPixmap(p); } else { mXFaceLabel->clear(); } } diff --git a/src/kmail_part.cpp b/src/kmail_part.cpp index 0fa8f5d78..703a1ee5b 100644 --- a/src/kmail_part.cpp +++ b/src/kmail_part.cpp @@ -1,161 +1,161 @@ /* This file is part of KMail. Copyright (c) 2002-2003 Don Sanders , Copyright (c) 2003 Zack Rusin , Based on the work of Cornelius Schumacher 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. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "kmail_part.h" #include "kmmainwin.h" #include "kmmainwidget.h" #include "kmstartup.h" #include "aboutdata.h" #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include "MailCommon/FolderTreeView" #include "tag/tagactionmanager.h" #include "foldershortcutactionmanager.h" #include "kmmigrateapplication.h" #include #include K_PLUGIN_FACTORY(KMailFactory, registerPlugin(); ) using namespace KMail; KMailPart::KMailPart(QWidget *parentWidget, QObject *parent, const QVariantList &) : KParts::ReadOnlyPart(parent) , mParentWidget(parentWidget) { setComponentName(QStringLiteral("kmail2"), i18n("KMail2")); KMMigrateApplication migrate; migrate.migrate(); // import i18n data and icons from libraries: KMail::insertLibraryIcons(); //local, do the init KMKernel *mKMailKernel = new KMKernel(); mKMailKernel->init(); mKMailKernel->setXmlGuiInstanceName(QStringLiteral("kmail2")); // and session management mKMailKernel->doSessionManagement(); // any dead letters? mKMailKernel->recoverDeadLetters(); kmkernel->setupDBus(); // Ok. We are ready for D-Bus requests. (void)new KmailpartAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/KMailPart"), this); // create a canvas to insert our widget QWidget *canvas = new QWidget(parentWidget); canvas->setFocusPolicy(Qt::ClickFocus); canvas->setObjectName(QStringLiteral("canvas")); setWidget(canvas); mainWidget = new KMMainWidget(canvas, this, actionCollection(), KSharedConfig::openConfig()); mainWidget->setObjectName(QStringLiteral("partmainwidget")); QVBoxLayout *topLayout = new QVBoxLayout(canvas); topLayout->addWidget(mainWidget); - topLayout->setMargin(0); + topLayout->setContentsMargins(0, 0, 0, 0); mainWidget->setFocusPolicy(Qt::ClickFocus); KParts::StatusBarExtension *statusBar = new KParts::StatusBarExtension(this); statusBar->addStatusBarItem(mainWidget->vacationScriptIndicator(), 2, false); statusBar->addStatusBarItem(mainWidget->zoomLabelIndicator(), 3, false); setXMLFile(QStringLiteral("kmail_part.rc"), true); KSettings::Dispatcher::registerComponent(QStringLiteral("kmail2"), mKMailKernel, "slotConfigChanged"); connect(mainWidget, &KMMainWidget::captionChangeRequest, this, &KMailPart::setWindowCaption); } KMailPart::~KMailPart() { qCDebug(KMAIL_LOG) << "Closing last KMMainWin: stopping mail check"; // Running KIO jobs prevent kapp from exiting, so we need to kill them // if they are only about checking mail (not important stuff like moving messages) mainWidget->destruct(); kmkernel->cleanup(); delete kmkernel; } void KMailPart::updateQuickSearchText() { mainWidget->updateQuickSearchLineText(); } bool KMailPart::openFile() { mainWidget->show(); return true; } //----------------------------------------------------------------------------- void KMailPart::guiActivateEvent(KParts::GUIActivateEvent *e) { KParts::ReadOnlyPart::guiActivateEvent(e); mainWidget->initializeFilterActions(); mainWidget->tagActionManager()->createActions(); mainWidget->folderShortcutActionManager()->createActions(); mainWidget->populateMessageListStatusFilterCombo(); mainWidget->initializePluginActions(); /* FIXME it doesn't work when we switch component. const QString title = mainWidget->fullCollectionPath(); if (!title.isEmpty()) { Q_EMIT setWindowCaption(title); } */ } void KMailPart::exit() { delete this; } QWidget *KMailPart::parentWidget() const { return mParentWidget; } void KMailPart::save() { /*TODO*/ } #include "kmail_part.moc" diff --git a/src/kmmainwidget.cpp b/src/kmmainwidget.cpp index fdc77afbb..56f288939 100644 --- a/src/kmmainwidget.cpp +++ b/src/kmmainwidget.cpp @@ -1,4861 +1,4861 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2009-2019 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); + mTopLayout->setContentsMargins(0, 0, 0, 0); mConfig = config; mGUIClient = aGUIClient; mFolderTreeWidget = 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) { 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) { assignLoadExternalReference(); } 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::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::assignLoadExternalReference() { if (mFolderHtmlLoadExtPreference) { mMsgView->setHtmlLoadExtDefault(mFolderHtmlLoadExtPreference); } else { mMsgView->setHtmlLoadExtDefault(mHtmlLoadExtGlobalSetting); } mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference); } //----------------------------------------------------------------------------- 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) { assignLoadExternalReference(); 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); } if (mMsgView && mMsgView->viewer()) { 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()); } else { menu.addAction(mMsgActions->editAsNewAction()); } 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 { if (mMsgView) { 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->editAsNewAction()->setEnabled(single_actions); 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(); } else if (single_actions) { actionList << mMsgActions->editAsNewAction(); } 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); if (mFolderHtmlLoadExtPreference) { mPreferHtmlLoadExtAction->setChecked(true); } else { mPreferHtmlLoadExtAction->setChecked(mHtmlLoadExtGlobalSetting); } } } //----------------------------------------------------------------------------- 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); assignLoadExternalReference(); 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.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; } KMailSettings::self()->setAskEnableUnifiedMailboxes(false); 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.\nDo 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\ndisable 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); } }; 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/kmsystemtray.h b/src/kmsystemtray.h index 714ee7c95..a7f7fcc1b 100644 --- a/src/kmsystemtray.h +++ b/src/kmsystemtray.h @@ -1,70 +1,71 @@ /*************************************************************************** kmsystemtray.h - description ------------------- begin : Fri Aug 31 22:38:44 EDT 2001 copyright : (C) 2001 by Ryan Breen email : ryan@porivo.com ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KMSYSTEMTRAY_H #define KMSYSTEMTRAY_H #include #include #include #include class QDBusServiceWatcher; class QMenu; /** * KMSystemTray extends KStatusNotifierItem and handles system * tray notification for KMail */ namespace KMail { class UnityServiceManager; class KMSystemTray : public KStatusNotifierItem { Q_OBJECT public: /** construtor */ explicit KMSystemTray(QObject *parent = nullptr); /** destructor */ ~KMSystemTray(); void hideKMail(); void updateStatus(int count); void updateCount(int count); void setUnityServiceManager(KMail::UnityServiceManager *unityServiceManager); void initialize(int count); void updateToolTip(int count); + private: void slotActivated(); void slotContextMenuAboutToShow(); void slotSelectCollection(QAction *act); bool mainWindowIsOnCurrentDesktop(); bool buildPopupMenu(); void fillFoldersMenu(QMenu *menu, const QAbstractItemModel *model, const QString &parentName = QString(), const QModelIndex &parentIndex = QModelIndex()); int mDesktopOfMainWin = 0; bool mHasUnreadMessage = false; bool mIconNotificationsEnabled = true; QMenu *mNewMessagesPopup = nullptr; QAction *mSendQueued = nullptr; KMail::UnityServiceManager *mUnityServiceManager = nullptr; }; } #endif diff --git a/src/kontactplugin/kmail/kcmkmailsummary.cpp b/src/kontactplugin/kmail/kcmkmailsummary.cpp index 71a524255..a20e0bf4e 100644 --- a/src/kontactplugin/kmail/kcmkmailsummary.cpp +++ b/src/kontactplugin/kmail/kcmkmailsummary.cpp @@ -1,144 +1,144 @@ /* This file is part of Kontact. Copyright (c) 2004 Tobias Koenig Copyright (C) 2013-2019 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. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "kcmkmailsummary.h" #include "PimCommonAkonadi/CheckedCollectionWidget" #include #include #include #include #include "kmailplugin_debug.h" #include #include #include #include #include #include extern "C" { Q_DECL_EXPORT KCModule *create_kmailsummary(QWidget *parent, const char *) { return new KCMKMailSummary(parent); } } KCMKMailSummary::KCMKMailSummary(QWidget *parent) : KCModule(parent) { initGUI(); connect(mCheckedCollectionWidget->folderTreeView(), &QAbstractItemView::clicked, this, &KCMKMailSummary::modified); connect(mFullPath, &QCheckBox::toggled, this, &KCMKMailSummary::modified); KAcceleratorManager::manage(this); load(); KAboutData *about = new KAboutData(QStringLiteral("kcmkmailsummary"), i18n("kcmkmailsummary"), QString(), i18n("Mail Summary Configuration Dialog"), KAboutLicense::GPL, i18n("Copyright © 2004–2010 Tobias Koenig")); about->addAuthor(ki18n("Tobias Koenig").toString(), QString(), QStringLiteral("tokoe@kde.org")); setAboutData(about); } void KCMKMailSummary::modified() { Q_EMIT changed(true); } void KCMKMailSummary::initGUI() { QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); mCheckedCollectionWidget = new PimCommon::CheckedCollectionWidget(KMime::Message::mimeType()); mFullPath = new QCheckBox(i18n("Show full path for folders"), this); mFullPath->setToolTip( i18nc("@info:tooltip", "Show full path for each folder")); mFullPath->setWhatsThis( i18nc("@info:whatsthis", "Enable this option if you want to see the full path " "for each folder listed in the summary. If this option is " "not enabled, then only the base folder path will be shown.")); layout->addWidget(mCheckedCollectionWidget); layout->addWidget(mFullPath); } void KCMKMailSummary::initFolders() { KSharedConfigPtr _config = KSharedConfig::openConfig(QStringLiteral("kcmkmailsummaryrc")); mModelState = new KViewStateMaintainer(_config->group("CheckState"), this); mModelState->setSelectionModel(mCheckedCollectionWidget->selectionModel()); } void KCMKMailSummary::loadFolders() { KConfig _config(QStringLiteral("kcmkmailsummaryrc")); KConfigGroup config(&_config, "General"); mModelState->restoreState(); const bool showFolderPaths = config.readEntry("showFolderPaths", false); mFullPath->setChecked(showFolderPaths); } void KCMKMailSummary::storeFolders() { KConfig _config(QStringLiteral("kcmkmailsummaryrc")); KConfigGroup config(&_config, "General"); mModelState->saveState(); config.writeEntry("showFolderPaths", mFullPath->isChecked()); config.sync(); } void KCMKMailSummary::load() { initFolders(); loadFolders(); Q_EMIT changed(false); } void KCMKMailSummary::save() { storeFolders(); Q_EMIT changed(false); } void KCMKMailSummary::defaults() { mFullPath->setChecked(true); Q_EMIT changed(true); } diff --git a/src/kontactplugin/kmail/summarywidget.cpp b/src/kontactplugin/kmail/summarywidget.cpp index 9291fd761..0604a0e38 100644 --- a/src/kontactplugin/kmail/summarywidget.cpp +++ b/src/kontactplugin/kmail/summarywidget.cpp @@ -1,241 +1,241 @@ /* This file is part of Kontact. Copyright (c) 2003 Tobias Koenig Copyright (C) 2013-2019 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. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "summarywidget.h" #include "kmailinterface.h" #include #include #include #include #include #include #include #include #include #include #include "kmailplugin_debug.h" #include #include #include #include #include #include #include #include #include SummaryWidget::SummaryWidget(KontactInterface::Plugin *plugin, QWidget *parent) : KontactInterface::Summary(parent) , mPlugin(plugin) { QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(3); - mainLayout->setMargin(3); + mainLayout->setContentsMargins(3, 3, 3, 3); QWidget *header = createHeader(this, QStringLiteral("view-pim-mail"), i18n("New Messages")); mainLayout->addWidget(header); mLayout = new QGridLayout(); mainLayout->addItem(mLayout); mLayout->setSpacing(3); mLayout->setRowStretch(6, 1); // Create a new change recorder. mChangeRecorder = new Akonadi::ChangeRecorder(this); mChangeRecorder->setMimeTypeMonitored(KMime::Message::mimeType()); mChangeRecorder->fetchCollectionStatistics(true); mChangeRecorder->setAllMonitored(true); mChangeRecorder->collectionFetchScope().setIncludeStatistics(true); mModel = new Akonadi::EntityTreeModel(mChangeRecorder, this); mModel->setItemPopulationStrategy(Akonadi::EntityTreeModel::NoItemPopulation); mSelectionModel = new QItemSelectionModel(mModel); mModelProxy = new KCheckableProxyModel(this); mModelProxy->setSelectionModel(mSelectionModel); mModelProxy->setSourceModel(mModel); KSharedConfigPtr _config = KSharedConfig::openConfig(QStringLiteral("kcmkmailsummaryrc")); mModelState = new KViewStateMaintainer(_config->group("CheckState"), this); mModelState->setSelectionModel(mSelectionModel); connect(mChangeRecorder, QOverload::of(&Akonadi::ChangeRecorder::collectionChanged), this, &SummaryWidget::slotCollectionChanged); connect(mChangeRecorder, &Akonadi::ChangeRecorder::collectionRemoved, this, &SummaryWidget::slotCollectionChanged); connect(mChangeRecorder, &Akonadi::ChangeRecorder::collectionStatisticsChanged, this, &SummaryWidget::slotCollectionChanged); QTimer::singleShot(0, this, &SummaryWidget::slotUpdateFolderList); } int SummaryWidget::summaryHeight() const { return 1; } void SummaryWidget::slotCollectionChanged() { QTimer::singleShot(0, this, &SummaryWidget::slotUpdateFolderList); } void SummaryWidget::updateSummary(bool force) { Q_UNUSED(force); QTimer::singleShot(0, this, &SummaryWidget::slotUpdateFolderList); } void SummaryWidget::selectFolder(const QString &folder) { if (mPlugin->isRunningStandalone()) { mPlugin->bringToForeground(); } else { mPlugin->core()->selectPlugin(mPlugin); } org::kde::kmail::kmail kmail(QStringLiteral("org.kde.kmail"), QStringLiteral("/KMail"), QDBusConnection::sessionBus()); kmail.selectFolder(folder); } void SummaryWidget::displayModel(const QModelIndex &parent, int &counter, const bool showFolderPaths, QStringList parentTreeNames) { const int nbCol = mModelProxy->rowCount(parent); for (int i = 0; i < nbCol; ++i) { const QModelIndex child = mModelProxy->index(i, 0, parent); const Akonadi::Collection col = mModelProxy->data(child, Akonadi::EntityTreeModel::CollectionRole).value(); const int showCollection = mModelProxy->data(child, Qt::CheckStateRole).toInt(); if (col.isValid()) { const Akonadi::CollectionStatistics stats = col.statistics(); if (((stats.unreadCount()) != Q_INT64_C(0)) && showCollection) { // Collection Name. KUrlLabel *urlLabel = nullptr; if (showFolderPaths) { // Construct the full path string. parentTreeNames.insert(parentTreeNames.size(), col.name()); urlLabel = new KUrlLabel(QString::number(col.id()), parentTreeNames.join(QLatin1Char('/')), this); parentTreeNames.removeLast(); } else { urlLabel = new KUrlLabel(QString::number(col.id()), col.name(), this); } urlLabel->installEventFilter(this); urlLabel->setAlignment(Qt::AlignLeft); urlLabel->setWordWrap(true); mLayout->addWidget(urlLabel, counter, 1); mLabels.append(urlLabel); // tooltip urlLabel->setToolTip(i18n("%1" "
Total: %2
" "Unread: %3
", col.name(), stats.count(), stats.unreadCount())); connect(urlLabel, QOverload::of(&KUrlLabel::leftClickedUrl), this, &SummaryWidget::selectFolder); // Read and unread count. QLabel *label = new QLabel(i18nc("%1: number of unread messages " "%2: total number of messages", "%1 / %2", stats.unreadCount(), stats.count()), this); label->setAlignment(Qt::AlignLeft); mLayout->addWidget(label, counter, 2); mLabels.append(label); // Folder icon. QIcon icon = mModelProxy->data(child, Qt::DecorationRole).value(); label = new QLabel(this); label->setPixmap(icon.pixmap(label->height() / 1.5)); label->setMaximumWidth(label->minimumSizeHint().width()); label->setAlignment(Qt::AlignVCenter); mLayout->addWidget(label, counter, 0); mLabels.append(label); ++counter; } parentTreeNames.insert(parentTreeNames.size(), col.name()); displayModel(child, counter, showFolderPaths, parentTreeNames); // Remove the last parent collection name for the next iteration. parentTreeNames.removeLast(); } } } void SummaryWidget::slotUpdateFolderList() { qDeleteAll(mLabels); mLabels.clear(); mModelState->restoreState(); int counter = 0; qCDebug(KMAILPLUGIN_LOG) << QStringLiteral("Iterating over") << mModel->rowCount() << QStringLiteral("collections."); KConfig _config(QStringLiteral("kcmkmailsummaryrc")); KConfigGroup config(&_config, "General"); const bool showFolderPaths = config.readEntry("showFolderPaths", false); displayModel(QModelIndex(), counter, showFolderPaths, QStringList()); if (counter == 0) { QLabel *label = new QLabel(i18n("No unread messages in your monitored folders"), this); label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); mLayout->addWidget(label, 0, 0); mLabels.append(label); } QList::const_iterator lit; QList::const_iterator lend(mLabels.constEnd()); for (lit = mLabels.constBegin(); lit != lend; ++lit) { (*lit)->show(); } } bool SummaryWidget::eventFilter(QObject *obj, QEvent *e) { if (obj->inherits("KUrlLabel")) { KUrlLabel *label = static_cast(obj); if (e->type() == QEvent::Enter) { Q_EMIT message(i18n("Open Folder: \"%1\"", label->text())); } else if (e->type() == QEvent::Leave) { Q_EMIT message(QString()); } } return KontactInterface::Summary::eventFilter(obj, e); } QStringList SummaryWidget::configModules() const { return QStringList() << QStringLiteral("kcmkmailsummary.desktop"); } diff --git a/src/kontactplugin/summary/kcmkontactsummary.cpp b/src/kontactplugin/summary/kcmkontactsummary.cpp index 1ea8cf5bd..9fb2072bd 100644 --- a/src/kontactplugin/summary/kcmkontactsummary.cpp +++ b/src/kontactplugin/summary/kcmkontactsummary.cpp @@ -1,191 +1,191 @@ /* This file is part of KDE Kontact. Copyright (c) 2004 Tobias Koenig Copyright (c) 2008 Allen Winter 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. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "kcmkontactsummary.h" #include #include #include #include #include #include #include #include #include #include extern "C" { Q_DECL_EXPORT KCModule *create_kontactsummary(QWidget *parent, const char *) { return new KCMKontactSummary(parent); } } class PluginItem : public QTreeWidgetItem { public: PluginItem(const KPluginInfo &info, QTreeWidget *parent) : QTreeWidgetItem(parent) , mInfo(info) { setIcon(0, QIcon::fromTheme(mInfo.icon())); setText(0, mInfo.name()); setToolTip(0, mInfo.comment()); setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); } KPluginInfo pluginInfo() const { return mInfo; } virtual QString text(int column) const { if (column == 0) { return mInfo.name(); } else if (column == 1) { return mInfo.comment(); } else { return QString(); } } private: Q_DISABLE_COPY(PluginItem) KPluginInfo mInfo; }; PluginView::PluginView(QWidget *parent) : QTreeWidget(parent) { setColumnCount(1); setHeaderLabel(i18nc("@title:column plugin name", "Summary Plugin Name")); setRootIsDecorated(false); } PluginView::~PluginView() { } KCMKontactSummary::KCMKontactSummary(QWidget *parent) : KCModule(parent) { setButtons(NoAdditionalButton); QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); QLabel *label = new QLabel(i18n("Select the plugin summaries to show on the summary page."), this); layout->addWidget(label); mPluginView = new PluginView(this); layout->addWidget(mPluginView); layout->setStretchFactor(mPluginView, 1); load(); connect(mPluginView, &QTreeWidget::itemChanged, this, QOverload<>::of(&KCMKontactSummary::changed)); KAboutData *about = new KAboutData(QStringLiteral("kontactsummary"), i18n("kontactsummary"), QString(), i18n("KDE Kontact Summary"), KAboutLicense::GPL, i18n("(c), 2004 Tobias Koenig")); about->addAuthor(ki18n("Tobias Koenig").toString(), QString(), QStringLiteral("tokoe@kde.org")); setAboutData(about); } void KCMKontactSummary::load() { KService::List offers = KServiceTypeTrader::self()->query( QStringLiteral("Kontact/Plugin"), QStringLiteral("[X-KDE-KontactPluginVersion] == %1").arg(KONTACT_PLUGIN_VERSION)); QStringList activeSummaries; KConfig config(QStringLiteral("kontact_summaryrc")); KConfigGroup grp(&config, QString()); if (grp.hasKey("ActiveSummaries")) { activeSummaries = grp.readEntry("ActiveSummaries", QStringList()); } else { activeSummaries << QStringLiteral("kontact_kaddressbookplugin"); activeSummaries << QStringLiteral("kontact_specialdatesplugin"); activeSummaries << QStringLiteral("kontact_korganizerplugin"); activeSummaries << QStringLiteral("kontact_todoplugin"); activeSummaries << QStringLiteral("kontact_knotesplugin"); activeSummaries << QStringLiteral("kontact_kmailplugin"); activeSummaries << QStringLiteral("kontact_weatherplugin"); activeSummaries << QStringLiteral("kontact_newstickerplugin"); activeSummaries << QStringLiteral("kontact_plannerplugin"); } mPluginView->clear(); KPluginInfo::List pluginList = KPluginInfo::fromServices(offers, KConfigGroup(&config, "Plugins")); KPluginInfo::List::Iterator it; KPluginInfo::List::Iterator end(pluginList.end()); for (it = pluginList.begin(); it != end; ++it) { it->load(); if (!it->isPluginEnabled()) { continue; } QVariant var = it->property(QStringLiteral("X-KDE-KontactPluginHasSummary")); if (var.isValid() && var.toBool() == true) { PluginItem *item = new PluginItem(*it, mPluginView); if (activeSummaries.contains(it->pluginName())) { item->setCheckState(0, Qt::Checked); } else { item->setCheckState(0, Qt::Unchecked); } } } } void KCMKontactSummary::save() { QStringList activeSummaries; QTreeWidgetItemIterator it(mPluginView); while (*it) { PluginItem *item = static_cast(*it); if (item->checkState(0) == Qt::Checked) { activeSummaries.append(item->pluginInfo().pluginName()); } ++it; } KConfig config(QStringLiteral("kontact_summaryrc")); KConfigGroup grp(&config, QString()); grp.writeEntry("ActiveSummaries", activeSummaries); } diff --git a/src/searchdialog/incompleteindexdialog.cpp b/src/searchdialog/incompleteindexdialog.cpp index f1609977d..8aa25e84f 100644 --- a/src/searchdialog/incompleteindexdialog.cpp +++ b/src/searchdialog/incompleteindexdialog.cpp @@ -1,252 +1,252 @@ /* * Copyright (c) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company * * 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 "incompleteindexdialog.h" #include "ui_incompleteindexdialog.h" #include "kmkernel.h" #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Qt::CheckState) Q_DECLARE_METATYPE(QVector) class SearchCollectionProxyModel : public QSortFilterProxyModel { public: explicit SearchCollectionProxyModel(const QVector &unindexedCollections, QObject *parent = nullptr) : QSortFilterProxyModel(parent) { mFilterCollections.reserve(unindexedCollections.size()); for (qint64 col : unindexedCollections) { mFilterCollections.insert(col, true); } } QVariant data(const QModelIndex &index, int role) const override { if (role == Qt::CheckStateRole) { if (index.isValid() && index.column() == 0) { const qint64 colId = collectionIdForIndex(index); return mFilterCollections.value(colId) ? Qt::Checked : Qt::Unchecked; } } return QSortFilterProxyModel::data(index, role); } bool setData(const QModelIndex &index, const QVariant &data, int role) override { if (role == Qt::CheckStateRole) { if (index.isValid() && index.column() == 0) { const qint64 colId = collectionIdForIndex(index); mFilterCollections[colId] = data.value(); return true; } } return QSortFilterProxyModel::setData(index, data, role); } Qt::ItemFlags flags(const QModelIndex &index) const override { if (index.isValid() && index.column() == 0) { return QSortFilterProxyModel::flags(index) | Qt::ItemIsUserCheckable; } else { return QSortFilterProxyModel::flags(index); } } protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { const QModelIndex source_idx = sourceModel()->index(source_row, 0, source_parent); const qint64 colId = sourceModel()->data(source_idx, Akonadi::EntityTreeModel::CollectionIdRole).toLongLong(); return mFilterCollections.contains(colId); } private: qint64 collectionIdForIndex(const QModelIndex &index) const { return data(index, Akonadi::EntityTreeModel::CollectionIdRole).toLongLong(); } private: QHash mFilterCollections; }; IncompleteIndexDialog::IncompleteIndexDialog(const QVector &unindexedCollections, QWidget *parent) : QDialog(parent) , mUi(new Ui::IncompleteIndexDialog) { QHBoxLayout *mainLayout = new QHBoxLayout(this); - mainLayout->setMargin(0); + mainLayout->setContentsMargins(0, 0, 0, 0); QWidget *w = new QWidget(this); mainLayout->addWidget(w); qDBusRegisterMetaType >(); mUi->setupUi(w); Akonadi::EntityTreeModel *etm = KMKernel::self()->entityTreeModel(); Akonadi::EntityMimeTypeFilterModel *mimeProxy = new Akonadi::EntityMimeTypeFilterModel(this); mimeProxy->addMimeTypeInclusionFilter(Akonadi::Collection::mimeType()); mimeProxy->setSourceModel(etm); KDescendantsProxyModel *flatProxy = new KDescendantsProxyModel(this); flatProxy->setDisplayAncestorData(true); flatProxy->setAncestorSeparator(QStringLiteral(" / ")); flatProxy->setSourceModel(mimeProxy); SearchCollectionProxyModel *proxy = new SearchCollectionProxyModel(unindexedCollections, this); proxy->setSourceModel(flatProxy); mUi->collectionView->setModel(proxy); mUi->collectionView->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(mUi->selectAllBtn, &QPushButton::clicked, this, &IncompleteIndexDialog::selectAll); connect(mUi->unselectAllBtn, &QPushButton::clicked, this, &IncompleteIndexDialog::unselectAll); mUi->buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Reindex")); mUi->buttonBox->button(QDialogButtonBox::Cancel)->setText(i18n("Search Anyway")); connect(mUi->buttonBox, &QDialogButtonBox::accepted, this, &IncompleteIndexDialog::waitForIndexer); connect(mUi->buttonBox, &QDialogButtonBox::rejected, this, &IncompleteIndexDialog::reject); readConfig(); } IncompleteIndexDialog::~IncompleteIndexDialog() { writeConfig(); } void IncompleteIndexDialog::readConfig() { KConfigGroup group(KSharedConfig::openConfig(), "IncompleteIndexDialog"); const QSize size = group.readEntry("Size", QSize(500, 400)); if (size.isValid()) { resize(size); } } void IncompleteIndexDialog::writeConfig() { KConfigGroup group(KSharedConfig::openConfig(), "IncompleteIndexDialog"); group.writeEntry("Size", size()); group.sync(); } void IncompleteIndexDialog::selectAll() { updateAllSelection(true); } void IncompleteIndexDialog::unselectAll() { updateAllSelection(false); } void IncompleteIndexDialog::updateAllSelection(bool select) { QAbstractItemModel *model = mUi->collectionView->model(); for (int i = 0, cnt = model->rowCount(); i < cnt; ++i) { const QModelIndex idx = model->index(i, 0, QModelIndex()); model->setData(idx, select ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } } QList IncompleteIndexDialog::collectionsToReindex() const { QList res; QAbstractItemModel *model = mUi->collectionView->model(); for (int i = 0, cnt = model->rowCount(); i < cnt; ++i) { const QModelIndex idx = model->index(i, 0, QModelIndex()); if (model->data(idx, Qt::CheckStateRole).toInt() == Qt::Checked) { res.push_back(model->data(idx, Akonadi::EntityTreeModel::CollectionRole).value().id()); } } return res; } void IncompleteIndexDialog::waitForIndexer() { mIndexer = new QDBusInterface(PimCommon::MailUtil::indexerServiceName(), QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.Indexer"), QDBusConnection::sessionBus(), this); if (!mIndexer->isValid()) { qCWarning(KMAIL_LOG) << "Invalid indexer dbus interface "; accept(); return; } mIndexingQueue = collectionsToReindex(); if (mIndexingQueue.isEmpty()) { accept(); return; } mProgressDialog = new QProgressDialog(this); mProgressDialog->setWindowTitle(i18n("Indexing")); mProgressDialog->setMaximum(mIndexingQueue.size()); mProgressDialog->setValue(0); mProgressDialog->setLabelText(i18n("Indexing Collections...")); connect(mProgressDialog, &QDialog::rejected, this, &IncompleteIndexDialog::slotStopIndexing); connect(mIndexer, SIGNAL(collectionIndexingFinished(qlonglong)), this, SLOT(slotCurrentlyIndexingCollectionChanged(qlonglong))); mIndexer->asyncCall(QStringLiteral("reindexCollections"), QVariant::fromValue(mIndexingQueue)); mProgressDialog->show(); } void IncompleteIndexDialog::slotStopIndexing() { mProgressDialog->close(); reject(); } void IncompleteIndexDialog::slotCurrentlyIndexingCollectionChanged(qlonglong colId) { const int idx = mIndexingQueue.indexOf(colId); if (idx > -1) { mIndexingQueue.removeAt(idx); mProgressDialog->setValue(mProgressDialog->maximum() - mIndexingQueue.size()); if (mIndexingQueue.isEmpty()) { QTimer::singleShot(1000, this, &IncompleteIndexDialog::accept); } } } diff --git a/src/searchdialog/searchwindow.cpp b/src/searchdialog/searchwindow.cpp index 92cc0478f..ba77dde41 100644 --- a/src/searchdialog/searchwindow.cpp +++ b/src/searchdialog/searchwindow.cpp @@ -1,960 +1,960 @@ /* * kmail: KDE mail client * Copyright (c) 1996-1998 Stefan Taferner * Copyright (c) 2001 Aaron J. Seigo * Copyright (c) 2010 Till Adam * Copyright (C) 2011-2019 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. * */ #include "searchwindow.h" #include "incompleteindexdialog.h" #include "MailCommon/FolderRequester" #include "kmcommands.h" #include "kmmainwidget.h" #include "MailCommon/MailKernel" #include "MailCommon/SearchPatternEdit" #include "searchdescriptionattribute.h" #include "MailCommon/FolderTreeView" #include "kmsearchmessagemodel.h" #include "searchpatternwarning.h" #include "PimCommonAkonadi/SelectMultiCollectionDialog" #include #include #include #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIM; using namespace MailCommon; using namespace KMail; SearchWindow::SearchWindow(KMMainWidget *widget, const Akonadi::Collection &collection) : QDialog(nullptr) , mKMMainWidget(widget) { setWindowTitle(i18n("Find Messages")); KWindowSystem::setIcons(winId(), qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop)), qApp->windowIcon().pixmap(IconSize(KIconLoader::Small), IconSize(KIconLoader::Small))); QVBoxLayout *mainLayout = new QVBoxLayout(this); QWidget *topWidget = new QWidget; QVBoxLayout *lay = new QVBoxLayout; - lay->setMargin(0); + lay->setContentsMargins(0, 0, 0, 0); topWidget->setLayout(lay); mSearchPatternWidget = new SearchPatternWarning; lay->addWidget(mSearchPatternWidget); mainLayout->addWidget(topWidget); QWidget *searchWidget = new QWidget(this); mUi.setupUi(searchWidget); lay->addWidget(searchWidget); mStartSearchGuiItem = KGuiItem(i18nc("@action:button Search for messages", "&Search"), QStringLiteral("edit-find")); mStopSearchGuiItem = KStandardGuiItem::stop(); mSearchButton = new QPushButton; KGuiItem::assign(mSearchButton, mStartSearchGuiItem); mUi.mButtonBox->addButton(mSearchButton, QDialogButtonBox::ActionRole); connect(mUi.mButtonBox, &QDialogButtonBox::rejected, this, &SearchWindow::slotClose); - searchWidget->layout()->setMargin(0); + searchWidget->layout()->setContentsMargins(0, 0, 0, 0); mUi.mCbxFolders->setMustBeReadWrite(false); mUi.mCbxFolders->setNotAllowToCreateNewFolder(true); activateFolder(collection); connect(mUi.mPatternEdit, &KMail::KMailSearchPatternEdit::returnPressed, this, &SearchWindow::slotSearch); // enable/disable widgets depending on radio buttons: connect(mUi.mChkbxAllFolders, &QRadioButton::toggled, this, &SearchWindow::setEnabledSearchButton); mUi.mLbxMatches->setXmlGuiClient(mKMMainWidget->guiClient()); /* Default is to sort by date. TODO: Unfortunately this sorts *while* inserting, which looks rather strange - the user cannot read the results so far as they are constantly re-sorted --dnaber Sorting is now disabled when a search is started and reenabled when it stops. Items are appended to the list. This not only solves the above problem, but speeds searches with many hits up considerably. - till TODO: subclass QTreeWidgetItem and do proper (and performant) compare functions */ mUi.mLbxMatches->setSortingEnabled(true); connect(mUi.mLbxMatches, &Akonadi::EntityTreeView::customContextMenuRequested, this, &SearchWindow::slotContextMenuRequested); connect(mUi.mLbxMatches, QOverload::of(&Akonadi::EntityTreeView::doubleClicked), this, &SearchWindow::slotViewMsg); connect(mUi.mLbxMatches, QOverload::of(&Akonadi::EntityTreeView::currentChanged), this, &SearchWindow::slotCurrentChanged); connect(mUi.selectMultipleFolders, &QPushButton::clicked, this, &SearchWindow::slotSelectMultipleFolders); connect(KMKernel::self()->folderCollectionMonitor(), &Akonadi::Monitor::collectionStatisticsChanged, this, &SearchWindow::updateCollectionStatistic); connect(mUi.mSearchFolderEdt, &KLineEdit::textChanged, this, &SearchWindow::scheduleRename); connect(&mRenameTimer, &QTimer::timeout, this, &SearchWindow::renameSearchFolder); connect(mUi.mSearchFolderOpenBtn, &QPushButton::clicked, this, &SearchWindow::openSearchFolder); connect(mUi.mSearchResultOpenBtn, &QPushButton::clicked, this, &SearchWindow::slotViewSelectedMsg); const int mainWidth = KMailSettings::self()->searchWidgetWidth(); const int mainHeight = KMailSettings::self()->searchWidgetHeight(); if (mainWidth || mainHeight) { resize(mainWidth, mainHeight); } connect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotSearch); connect(this, &SearchWindow::finished, this, &SearchWindow::deleteLater); connect(mUi.mButtonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &SearchWindow::slotClose); // give focus to the value field of the first search rule KLineEdit *r = mUi.mPatternEdit->findChild(QStringLiteral("regExpLineEdit")); if (r) { r->setFocus(); } else { qCDebug(KMAIL_LOG) << "SearchWindow: regExpLineEdit not found"; } //set up actions KActionCollection *ac = actionCollection(); mReplyAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("&Reply..."), this); actionCollection()->addAction(QStringLiteral("search_reply"), mReplyAction); connect(mReplyAction, &QAction::triggered, this, &SearchWindow::slotReplyToMsg); mReplyAllAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n("Reply to &All..."), this); actionCollection()->addAction(QStringLiteral("search_reply_all"), mReplyAllAction); connect(mReplyAllAction, &QAction::triggered, this, &SearchWindow::slotReplyAllToMsg); mReplyListAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-list")), i18n("Reply to Mailing-&List..."), this); actionCollection()->addAction(QStringLiteral("search_reply_list"), mReplyListAction); connect(mReplyListAction, &QAction::triggered, this, &SearchWindow::slotReplyListToMsg); mForwardActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("Message->", "&Forward"), this); actionCollection()->addAction(QStringLiteral("search_message_forward"), mForwardActionMenu); connect(mForwardActionMenu, &KActionMenu::triggered, this, &SearchWindow::slotForwardMsg); mForwardInlineAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu Forward message inline.", "&Inline..."), this); actionCollection()->addAction(QStringLiteral("search_message_forward_inline"), mForwardInlineAction); connect(mForwardInlineAction, &QAction::triggered, this, &SearchWindow::slotForwardMsg); mForwardAttachedAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("Message->Forward->", "As &Attachment..."), this); actionCollection()->addAction(QStringLiteral("search_message_forward_as_attachment"), mForwardAttachedAction); connect(mForwardAttachedAction, &QAction::triggered, this, &SearchWindow::slotForwardAttachedMsg); if (KMailSettings::self()->forwardingInlineByDefault()) { mForwardActionMenu->addAction(mForwardInlineAction); mForwardActionMenu->addAction(mForwardAttachedAction); } else { mForwardActionMenu->addAction(mForwardAttachedAction); mForwardActionMenu->addAction(mForwardInlineAction); } mSaveAsAction = actionCollection()->addAction(KStandardAction::SaveAs, QStringLiteral("search_file_save_as")); connect(mSaveAsAction, &QAction::triggered, this, &SearchWindow::slotSaveMsg); mSaveAtchAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Save Attachments..."), this); actionCollection()->addAction(QStringLiteral("search_save_attachments"), mSaveAtchAction); connect(mSaveAtchAction, &QAction::triggered, this, &SearchWindow::slotSaveAttachments); mPrintAction = actionCollection()->addAction(KStandardAction::Print, QStringLiteral("search_print")); connect(mPrintAction, &QAction::triggered, this, &SearchWindow::slotPrintMsg); mClearAction = new QAction(i18n("Clear Selection"), this); actionCollection()->addAction(QStringLiteral("search_clear_selection"), mClearAction); connect(mClearAction, &QAction::triggered, this, &SearchWindow::slotClearSelection); mJumpToFolderAction = new QAction(i18n("Jump to original folder"), this); actionCollection()->addAction(QStringLiteral("search_jump_folder"), mJumpToFolderAction); connect(mJumpToFolderAction, &QAction::triggered, this, &SearchWindow::slotJumpToFolder); connect(mUi.mCbxFolders, &MailCommon::FolderRequester::folderChanged, this, &SearchWindow::slotFolderActivated); ac->addAssociatedWidget(this); const QList actList = ac->actions(); for (QAction *action : actList) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } } SearchWindow::~SearchWindow() { if (mResultModel) { if (mUi.mLbxMatches->columnWidth(0) > 0) { KMailSettings::self()->setCollectionWidth(mUi.mLbxMatches->columnWidth(0)); } if (mUi.mLbxMatches->columnWidth(1) > 0) { KMailSettings::self()->setSubjectWidth(mUi.mLbxMatches->columnWidth(1)); } if (mUi.mLbxMatches->columnWidth(2) > 0) { KMailSettings::self()->setSenderWidth(mUi.mLbxMatches->columnWidth(2)); } if (mUi.mLbxMatches->columnWidth(3) > 0) { KMailSettings::self()->setReceiverWidth(mUi.mLbxMatches->columnWidth(3)); } if (mUi.mLbxMatches->columnWidth(4) > 0) { KMailSettings::self()->setDateWidth(mUi.mLbxMatches->columnWidth(4)); } if (mUi.mLbxMatches->columnWidth(5) > 0) { KMailSettings::self()->setFolderWidth(mUi.mLbxMatches->columnWidth(5)); } KMailSettings::self()->setSearchWidgetWidth(width()); KMailSettings::self()->setSearchWidgetHeight(height()); KMailSettings::self()->requestSync(); mResultModel->deleteLater(); } } void SearchWindow::createSearchModel() { if (mResultModel) { mResultModel->deleteLater(); } mResultModel = new KMSearchMessageModel(this); mResultModel->setCollection(mFolder); QSortFilterProxyModel *sortproxy = new QSortFilterProxyModel(mResultModel); sortproxy->setDynamicSortFilter(true); sortproxy->setSortRole(Qt::EditRole); sortproxy->setFilterCaseSensitivity(Qt::CaseInsensitive); sortproxy->setSourceModel(mResultModel); mUi.mLbxMatches->setModel(sortproxy); mUi.mLbxMatches->setColumnWidth(0, KMailSettings::self()->collectionWidth()); mUi.mLbxMatches->setColumnWidth(1, KMailSettings::self()->subjectWidth()); mUi.mLbxMatches->setColumnWidth(2, KMailSettings::self()->senderWidth()); mUi.mLbxMatches->setColumnWidth(3, KMailSettings::self()->receiverWidth()); mUi.mLbxMatches->setColumnWidth(4, KMailSettings::self()->dateWidth()); mUi.mLbxMatches->setColumnWidth(5, KMailSettings::self()->folderWidth()); mUi.mLbxMatches->setColumnHidden(6, true); mUi.mLbxMatches->setColumnHidden(7, true); mUi.mLbxMatches->header()->setSortIndicator(2, Qt::DescendingOrder); mUi.mLbxMatches->header()->setStretchLastSection(false); mUi.mLbxMatches->header()->restoreState(mHeaderState); //mUi.mLbxMatches->header()->setResizeMode( 3, QHeaderView::Stretch ); if (!mAkonadiStandardAction) { mAkonadiStandardAction = new Akonadi::StandardMailActionManager(actionCollection(), this); } mAkonadiStandardAction->setItemSelectionModel(mUi.mLbxMatches->selectionModel()); mAkonadiStandardAction->setCollectionSelectionModel(mKMMainWidget->folderTreeView()->selectionModel()); } void SearchWindow::setEnabledSearchButton(bool) { //Make sure that button is enable //Before when we selected a folder == "Local Folder" as that it was not a folder //search button was disable, and when we select "Search in all local folder" //Search button was never enabled :( mSearchButton->setEnabled(true); } void SearchWindow::updateCollectionStatistic(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistic) { QString genMsg; if (id == mFolder.id()) { genMsg = i18np("%1 match", "%1 matches", statistic.count()); } mUi.mStatusLbl->setText(genMsg); } void SearchWindow::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape && mSearchJob) { slotStop(); return; } QDialog::keyPressEvent(event); } void SearchWindow::slotFolderActivated() { mUi.mChkbxSpecificFolders->setChecked(true); } void SearchWindow::activateFolder(const Akonadi::Collection &collection) { mUi.mChkbxSpecificFolders->setChecked(true); mSearchPattern.clear(); bool currentFolderIsSearchFolder = false; if (!collection.hasAttribute()) { // it's not a search folder, make a new search mSearchPattern.append(SearchRule::createInstance("Subject")); mUi.mCbxFolders->setCollection(collection); } else { // it's a search folder if (collection.hasAttribute()) { currentFolderIsSearchFolder = true; // FIXME is there a better way to tell? const Akonadi::SearchDescriptionAttribute *searchDescription = collection.attribute(); mSearchPattern.deserialize(searchDescription->description()); const QList lst = searchDescription->listCollection(); if (!lst.isEmpty()) { mUi.mChkMultiFolders->setChecked(true); mCollectionId.clear(); for (Akonadi::Collection::Id col : lst) { mCollectionId.append(Akonadi::Collection(col)); } } else { const Akonadi::Collection col = searchDescription->baseCollection(); if (col.isValid()) { mUi.mChkbxSpecificFolders->setChecked(true); mUi.mCbxFolders->setCollection(col); mUi.mChkSubFolders->setChecked(searchDescription->recursive()); } else { mUi.mChkbxAllFolders->setChecked(true); mUi.mChkSubFolders->setChecked(searchDescription->recursive()); } } } else { // it's a search folder, but not one of ours, warn the user that we can't edit it // FIXME show results, but disable edit GUI qCWarning(KMAIL_LOG) << "This search was not created with KMail. It cannot be edited within it."; mSearchPattern.clear(); } } mUi.mPatternEdit->setSearchPattern(&mSearchPattern); if (currentFolderIsSearchFolder) { mFolder = collection; mUi.mSearchFolderEdt->setText(collection.name()); createSearchModel(); } else if (mUi.mSearchFolderEdt->text().isEmpty()) { mUi.mSearchFolderEdt->setText(i18n("Last Search")); // find last search and reuse it if possible mFolder = CommonKernel->collectionFromId(KMailSettings::lastSearchCollectionId()); // when the last folder got renamed, create a new one if (mFolder.isValid() && mFolder.name() != mUi.mSearchFolderEdt->text()) { mFolder = Akonadi::Collection(); } } } void SearchWindow::slotSearch() { if (mFolder.isValid()) { doSearch(); return; } //We're going to try to create a new search folder, let's ensure first the name is not yet used. //Fetch all search collections Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Akonadi::Collection(1), Akonadi::CollectionFetchJob::FirstLevel); connect(fetchJob, &Akonadi::CollectionFetchJob::result, this, &SearchWindow::slotSearchCollectionsFetched); } void SearchWindow::slotSearchCollectionsFetched(KJob *job) { if (job->error()) { qCWarning(KMAIL_LOG) << job->errorString(); } Akonadi::CollectionFetchJob *fetchJob = static_cast(job); const Akonadi::Collection::List lstCol = fetchJob->collections(); for (const Akonadi::Collection &col : lstCol) { if (col.name() == mUi.mSearchFolderEdt->text()) { mFolder = col; } } doSearch(); } void SearchWindow::doSearch() { mSearchPatternWidget->hideWarningPattern(); if (mUi.mSearchFolderEdt->text().isEmpty()) { mUi.mSearchFolderEdt->setText(i18n("Last Search")); } if (mResultModel) { mHeaderState = mUi.mLbxMatches->header()->saveState(); } mUi.mLbxMatches->setModel(nullptr); mSortColumn = mUi.mLbxMatches->header()->sortIndicatorSection(); mSortOrder = mUi.mLbxMatches->header()->sortIndicatorOrder(); mUi.mLbxMatches->setSortingEnabled(false); if (mSearchJob) { mSearchJob->kill(KJob::Quietly); mSearchJob->deleteLater(); mSearchJob = nullptr; } mUi.mSearchFolderEdt->setEnabled(false); QVector searchCollections; bool recursive = false; if (mUi.mChkbxSpecificFolders->isChecked()) { const Akonadi::Collection col = mUi.mCbxFolders->collection(); if (!col.isValid()) { mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You did not selected a valid folder.")); mUi.mSearchFolderEdt->setEnabled(true); return; } searchCollections << col; if (mUi.mChkSubFolders->isChecked()) { recursive = true; } } else if (mUi.mChkMultiFolders->isChecked()) { if (mSelectMultiCollectionDialog) { mCollectionId = mSelectMultiCollectionDialog->selectedCollection(); } if (mCollectionId.isEmpty()) { mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to select collections.")); mQuery = Akonadi::SearchQuery(); return; } searchCollections << mCollectionId; } mUi.mPatternEdit->updateSearchPattern(); SearchPattern searchPattern(mSearchPattern); searchPattern.purify(); MailCommon::SearchPattern::SparqlQueryError queryError = searchPattern.asAkonadiQuery(mQuery); switch (queryError) { case MailCommon::SearchPattern::NoError: break; case MailCommon::SearchPattern::MissingCheck: mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to define condition.")); mQuery = Akonadi::SearchQuery(); return; case MailCommon::SearchPattern::FolderEmptyOrNotIndexed: mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("All folders selected are empty or were not indexed.")); mQuery = Akonadi::SearchQuery(); return; case MailCommon::SearchPattern::EmptyResult: mUi.mSearchFolderEdt->setEnabled(true); mQuery = Akonadi::SearchQuery(); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to add conditions.")); return; case MailCommon::SearchPattern::NotEnoughCharacters: mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("Contains condition cannot be used with a number of characters inferior to 4.")); mQuery = Akonadi::SearchQuery(); return; } mSearchPatternWidget->hideWarningPattern(); qCDebug(KMAIL_LOG) << mQuery.toJSON(); mUi.mSearchFolderOpenBtn->setEnabled(true); const QVector unindexedCollections = checkIncompleteIndex(searchCollections, recursive); if (!unindexedCollections.isEmpty()) { QScopedPointer dlg(new IncompleteIndexDialog(unindexedCollections)); dlg->exec(); } if (!mFolder.isValid()) { qCDebug(KMAIL_LOG) << " create new folder " << mUi.mSearchFolderEdt->text(); Akonadi::SearchCreateJob *searchJob = new Akonadi::SearchCreateJob(mUi.mSearchFolderEdt->text(), mQuery, this); searchJob->setSearchMimeTypes(QStringList() << QStringLiteral("message/rfc822")); searchJob->setSearchCollections(searchCollections); searchJob->setRecursive(recursive); searchJob->setRemoteSearchEnabled(false); mSearchJob = searchJob; } else { qCDebug(KMAIL_LOG) << " use existing folder " << mFolder.id(); Akonadi::PersistentSearchAttribute *attribute = new Akonadi::PersistentSearchAttribute(); mFolder.setContentMimeTypes(QStringList() << QStringLiteral("message/rfc822")); attribute->setQueryString(QString::fromLatin1(mQuery.toJSON())); attribute->setQueryCollections(searchCollections); attribute->setRecursive(recursive); attribute->setRemoteSearchEnabled(false); mFolder.addAttribute(attribute); mSearchJob = new Akonadi::CollectionModifyJob(mFolder, this); } connect(mSearchJob, &Akonadi::CollectionModifyJob::result, this, &SearchWindow::searchDone); mUi.mProgressIndicator->start(); enableGUI(); mUi.mStatusLbl->setText(i18n("Searching...")); } void SearchWindow::searchDone(KJob *job) { Q_ASSERT(job == mSearchJob); mSearchJob = nullptr; QMetaObject::invokeMethod(this, &SearchWindow::enableGUI, Qt::QueuedConnection); mUi.mProgressIndicator->stop(); if (job->error()) { qCDebug(KMAIL_LOG) << job->errorString(); KMessageBox::sorry(this, i18n("Cannot get search result. %1", job->errorString())); enableGUI(); mUi.mSearchFolderEdt->setEnabled(true); mUi.mStatusLbl->setText(i18n("Search failed.")); } else { if (const auto *searchJob = qobject_cast(job)) { mFolder = searchJob->createdCollection(); } else if (const auto *modifyJob = qobject_cast(job)) { mFolder = modifyJob->collection(); } /// TODO: cope better with cases where this fails Q_ASSERT(mFolder.isValid()); Q_ASSERT(mFolder.hasAttribute()); KMailSettings::setLastSearchCollectionId(mFolder.id()); KMailSettings::self()->save(); KMailSettings::self()->requestSync(); // store the kmail specific serialization of the search in an attribute on // the server, for easy retrieval when editing it again const QByteArray search = mSearchPattern.serialize(); Q_ASSERT(!search.isEmpty()); Akonadi::SearchDescriptionAttribute *searchDescription = mFolder.attribute(Akonadi::Collection::AddIfMissing); searchDescription->setDescription(search); if (mUi.mChkMultiFolders->isChecked()) { searchDescription->setBaseCollection(Akonadi::Collection()); QList lst; lst.reserve(mCollectionId.count()); for (const Akonadi::Collection &col : qAsConst(mCollectionId)) { lst << col.id(); } searchDescription->setListCollection(lst); } else if (mUi.mChkbxSpecificFolders->isChecked()) { const Akonadi::Collection collection = mUi.mCbxFolders->collection(); searchDescription->setBaseCollection(collection); } else { searchDescription->setBaseCollection(Akonadi::Collection()); } searchDescription->setRecursive(mUi.mChkSubFolders->isChecked()); new Akonadi::CollectionModifyJob(mFolder, this); Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(mFolder, Akonadi::CollectionFetchJob::Base, this); fetch->fetchScope().setIncludeStatistics(true); connect(fetch, &KJob::result, this, &SearchWindow::slotCollectionStatisticsRetrieved); mUi.mStatusLbl->setText(i18n("Search complete.")); createSearchModel(); if (mCloseRequested) { close(); } mUi.mLbxMatches->setSortingEnabled(true); mUi.mLbxMatches->header()->setSortIndicator(mSortColumn, mSortOrder); mUi.mSearchFolderEdt->setEnabled(true); } } void SearchWindow::slotCollectionStatisticsRetrieved(KJob *job) { Akonadi::CollectionFetchJob *fetch = qobject_cast(job); if (!fetch || fetch->error()) { return; } const Akonadi::Collection::List cols = fetch->collections(); if (cols.isEmpty()) { mUi.mStatusLbl->clear(); return; } const Akonadi::Collection col = cols.at(0); updateCollectionStatistic(col.id(), col.statistics()); } void SearchWindow::slotStop() { mUi.mProgressIndicator->stop(); if (mSearchJob) { mSearchJob->kill(KJob::Quietly); mSearchJob->deleteLater(); mSearchJob = nullptr; mUi.mStatusLbl->setText(i18n("Search stopped.")); } enableGUI(); } void SearchWindow::slotClose() { accept(); } void SearchWindow::closeEvent(QCloseEvent *event) { if (mSearchJob) { mCloseRequested = true; //Cancel search in progress mSearchJob->kill(KJob::Quietly); mSearchJob->deleteLater(); mSearchJob = nullptr; QTimer::singleShot(0, this, &SearchWindow::slotClose); } else { QDialog::closeEvent(event); } } void SearchWindow::scheduleRename(const QString &text) { if (!text.isEmpty()) { mRenameTimer.setSingleShot(true); mRenameTimer.start(250); mUi.mSearchFolderOpenBtn->setEnabled(false); } else { mRenameTimer.stop(); mUi.mSearchFolderOpenBtn->setEnabled(!text.isEmpty()); } } void SearchWindow::renameSearchFolder() { const QString name = mUi.mSearchFolderEdt->text(); if (mFolder.isValid()) { const QString oldFolderName = mFolder.name(); if (oldFolderName != name) { mFolder.setName(name); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob(mFolder, this); job->setProperty("oldfoldername", oldFolderName); connect(job, &Akonadi::CollectionModifyJob::result, this, &SearchWindow::slotSearchFolderRenameDone); } mUi.mSearchFolderOpenBtn->setEnabled(true); } } void SearchWindow::slotSearchFolderRenameDone(KJob *job) { Q_ASSERT(job); if (job->error()) { qCWarning(KMAIL_LOG) << "Job failed:" << job->errorText(); KMessageBox::information(this, i18n("There was a problem renaming your search folder. " "A common reason for this is that another search folder " "with the same name already exists. Error returned \"%1\".", job->errorText())); mUi.mSearchFolderEdt->blockSignals(true); mUi.mSearchFolderEdt->setText(job->property("oldfoldername").toString()); mUi.mSearchFolderEdt->blockSignals(false); } } void SearchWindow::openSearchFolder() { Q_ASSERT(mFolder.isValid()); renameSearchFolder(); mKMMainWidget->slotSelectCollectionFolder(mFolder); slotClose(); } void SearchWindow::slotViewSelectedMsg() { mKMMainWidget->slotMessageActivated(selectedMessage()); } void SearchWindow::slotViewMsg(const Akonadi::Item &item) { if (item.isValid()) { mKMMainWidget->slotMessageActivated(item); } } void SearchWindow::slotCurrentChanged(const Akonadi::Item &item) { mUi.mSearchResultOpenBtn->setEnabled(item.isValid()); } void SearchWindow::enableGUI() { const bool searching = (mSearchJob != nullptr); KGuiItem::assign(mSearchButton, searching ? mStopSearchGuiItem : mStartSearchGuiItem); if (searching) { disconnect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotSearch); connect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotStop); } else { disconnect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotStop); connect(mSearchButton, &QPushButton::clicked, this, &SearchWindow::slotSearch); } } Akonadi::Item::List SearchWindow::selectedMessages() const { Akonadi::Item::List messages; const QModelIndexList lst = mUi.mLbxMatches->selectionModel()->selectedRows(); for (const QModelIndex &index : lst) { const Akonadi::Item item = index.data(Akonadi::ItemModel::ItemRole).value(); if (item.isValid()) { messages.append(item); } } return messages; } Akonadi::Item SearchWindow::selectedMessage() const { return mUi.mLbxMatches->currentIndex().data(Akonadi::ItemModel::ItemRole).value(); } void SearchWindow::updateContextMenuActions() { const int count = selectedMessages().count(); const bool singleActions = (count == 1); const bool notEmpty = (count > 0); mJumpToFolderAction->setEnabled(singleActions); mReplyAction->setEnabled(singleActions); mReplyAllAction->setEnabled(singleActions); mReplyListAction->setEnabled(singleActions); mPrintAction->setEnabled(singleActions); mSaveAtchAction->setEnabled(notEmpty); mSaveAsAction->setEnabled(notEmpty); mClearAction->setEnabled(notEmpty); } void SearchWindow::slotContextMenuRequested(const QPoint &) { if (!selectedMessage().isValid() || selectedMessages().isEmpty()) { return; } updateContextMenuActions(); QMenu menu(this); // show most used actions menu.addAction(mReplyAction); menu.addAction(mReplyAllAction); menu.addAction(mReplyListAction); menu.addAction(mForwardActionMenu); menu.addSeparator(); menu.addAction(mJumpToFolderAction); menu.addSeparator(); QAction *act = mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::CopyItems); mAkonadiStandardAction->setActionText(Akonadi::StandardActionManager::CopyItems, ki18np("Copy Message", "Copy %1 Messages")); menu.addAction(act); act = mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::CutItems); mAkonadiStandardAction->setActionText(Akonadi::StandardActionManager::CutItems, ki18np("Cut Message", "Cut %1 Messages")); menu.addAction(act); menu.addAction(mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::CopyItemToMenu)); menu.addAction(mAkonadiStandardAction->createAction(Akonadi::StandardActionManager::MoveItemToMenu)); menu.addSeparator(); menu.addAction(mSaveAsAction); menu.addAction(mSaveAtchAction); menu.addAction(mPrintAction); menu.addSeparator(); menu.addAction(mClearAction); menu.exec(QCursor::pos(), nullptr); } void SearchWindow::slotClearSelection() { mUi.mLbxMatches->clearSelection(); } void SearchWindow::slotReplyToMsg() { KMCommand *command = new KMReplyCommand(this, selectedMessage(), MessageComposer::ReplySmart); command->start(); } void SearchWindow::slotReplyAllToMsg() { KMCommand *command = new KMReplyCommand(this, selectedMessage(), MessageComposer::ReplyAll); command->start(); } void SearchWindow::slotReplyListToMsg() { KMCommand *command = new KMReplyCommand(this, selectedMessage(), MessageComposer::ReplyList); command->start(); } void SearchWindow::slotForwardMsg() { KMCommand *command = new KMForwardCommand(this, selectedMessages()); command->start(); } void SearchWindow::slotForwardAttachedMsg() { KMCommand *command = new KMForwardAttachedCommand(this, selectedMessages()); command->start(); } void SearchWindow::slotSaveMsg() { KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand(this, selectedMessages()); saveCommand->start(); } void SearchWindow::slotSaveAttachments() { KMSaveAttachmentsCommand *saveCommand = new KMSaveAttachmentsCommand(this, selectedMessages()); saveCommand->start(); } void SearchWindow::slotPrintMsg() { KMPrintCommandInfo info; info.mMsg = selectedMessage(); KMCommand *command = new KMPrintCommand(this, info); command->start(); } void SearchWindow::addRulesToSearchPattern(const SearchPattern &pattern) { SearchPattern p(mSearchPattern); p.purify(); QList::const_iterator it; QList::const_iterator end(pattern.constEnd()); p.reserve(pattern.count()); for (it = pattern.constBegin(); it != end; ++it) { p.append(SearchRule::createInstance(**it)); } mSearchPattern = p; mUi.mPatternEdit->setSearchPattern(&mSearchPattern); } void SearchWindow::slotSelectMultipleFolders() { mUi.mChkMultiFolders->setChecked(true); if (!mSelectMultiCollectionDialog) { QList lst; lst.reserve(mCollectionId.count()); for (const Akonadi::Collection &col : qAsConst(mCollectionId)) { lst << col.id(); } mSelectMultiCollectionDialog = new PimCommon::SelectMultiCollectionDialog(KMime::Message::mimeType(), lst, this); } mSelectMultiCollectionDialog->show(); } void SearchWindow::slotJumpToFolder() { if (selectedMessage().isValid()) { mKMMainWidget->slotSelectCollectionFolder(selectedMessage().parentCollection()); } } QVector SearchWindow::checkIncompleteIndex(const Akonadi::Collection::List &searchCols, bool recursive) { QVector results; Akonadi::Collection::List cols; if (recursive) { cols = searchCollectionsRecursive(searchCols); } else { for (const Akonadi::Collection &col : searchCols) { QAbstractItemModel *etm = KMKernel::self()->collectionModel(); const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(etm, col); const Akonadi::Collection modelCol = etm->data(idx, Akonadi::EntityTreeModel::CollectionRole).value(); // Only index offline IMAP collections if (PimCommon::Util::isImapResource(modelCol.resource()) && !modelCol.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { continue; } else { cols.push_back(modelCol); } } } enableGUI(); mUi.mProgressIndicator->start(); mUi.mStatusLbl->setText(i18n("Checking index status...")); //Fetch collection ? for (const Akonadi::Collection &col : qAsConst(cols)) { const qlonglong num = KMKernel::self()->indexedItems()->indexedItems((qlonglong)col.id()); if (col.statistics().count() != num) { results.push_back(col.id()); } } return results; } Akonadi::Collection::List SearchWindow::searchCollectionsRecursive(const Akonadi::Collection::List &cols) const { QAbstractItemModel *etm = KMKernel::self()->collectionModel(); Akonadi::Collection::List result; for (const Akonadi::Collection &col : cols) { const QModelIndex colIdx = Akonadi::EntityTreeModel::modelIndexForCollection(etm, col); if (col.statistics().count() > -1) { if (col.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { result.push_back(col); } } else { const Akonadi::Collection collection = etm->data(colIdx, Akonadi::EntityTreeModel::CollectionRole).value(); if (!collection.hasAttribute() && collection.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { result.push_back(collection); } } const int childrenCount = etm->rowCount(colIdx); if (childrenCount > 0) { Akonadi::Collection::List subCols; subCols.reserve(childrenCount); for (int i = 0; i < childrenCount; ++i) { const QModelIndex idx = etm->index(i, 0, colIdx); const Akonadi::Collection child = etm->data(idx, Akonadi::EntityTreeModel::CollectionRole).value(); if (child.cachePolicy().localParts().contains(QLatin1String("RFC822"))) { subCols.push_back(child); } } result += searchCollectionsRecursive(subCols); } } return result; } diff --git a/src/widgets/vacationscriptindicatorwidget.cpp b/src/widgets/vacationscriptindicatorwidget.cpp index 58d1e0bd6..9f28da9f6 100644 --- a/src/widgets/vacationscriptindicatorwidget.cpp +++ b/src/widgets/vacationscriptindicatorwidget.cpp @@ -1,134 +1,134 @@ /* Copyright (C) 2013-2019 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 "vacationscriptindicatorwidget.h" #include "util.h" #include #include #include using namespace KMail; ServerLabel::ServerLabel(const QString &serverName, QWidget *parent) : QLabel(parent) , mServerName(serverName) { setToolTip(serverName); setPixmap(QIcon::fromTheme(QStringLiteral("network-server")).pixmap(16, 16)); setStyleSheet(QStringLiteral("background-color: %1;").arg(QColor(Qt::yellow).name())); setContentsMargins(2, 0, 4, 0); } ServerLabel::~ServerLabel() { } void ServerLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(mServerName); QLabel::mouseReleaseEvent(event); } VacationLabel::VacationLabel(const QString &text, QWidget *parent) : QLabel(text, parent) { // changing the palette doesn't work, seems to be overwriten by the // statusbar again, stylesheets seems to work though setStyleSheet(QStringLiteral("background-color: %1; color: %2;").arg(QColor(Qt::yellow).name(), QColor(Qt::black).name())); setContentsMargins(4, 0, 2, 0); setCursor(QCursor(Qt::PointingHandCursor)); } VacationLabel::~VacationLabel() { } void VacationLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT vacationLabelClicked(); QLabel::mouseReleaseEvent(event); } VacationScriptIndicatorWidget::VacationScriptIndicatorWidget(QWidget *parent) : QWidget(parent) , mBoxLayout(nullptr) , mInfo(nullptr) { } VacationScriptIndicatorWidget::~VacationScriptIndicatorWidget() { } void VacationScriptIndicatorWidget::setVacationScriptActive(bool active, const QString &serverName) { if (serverName.isEmpty()) { return; } if (active) { if (!mServerActive.contains(serverName)) { mServerActive.append(serverName); updateIndicator(); } } else { int countRemoveServerName = mServerActive.removeAll(serverName); if (countRemoveServerName > 0) { updateIndicator(); } } } void VacationScriptIndicatorWidget::createIndicator() { delete mBoxLayout; mBoxLayout = new QHBoxLayout(this); - mBoxLayout->setMargin(0); + mBoxLayout->setContentsMargins(0, 0, 0, 0); mBoxLayout->setSpacing(0); mInfo = new VacationLabel(i18np("Out of office reply active on server", "Out of office reply active on servers", mServerActive.count())); connect(mInfo, &VacationLabel::vacationLabelClicked, this, &VacationScriptIndicatorWidget::slotVacationLabelClicked); mBoxLayout->addWidget(mInfo); for (const QString &server : qAsConst(mServerActive)) { ServerLabel *lab = new ServerLabel(server); connect(lab, &ServerLabel::clicked, this, &VacationScriptIndicatorWidget::clicked); mBoxLayout->addWidget(lab); } } void VacationScriptIndicatorWidget::slotVacationLabelClicked() { Q_EMIT clicked(QString()); } void VacationScriptIndicatorWidget::updateIndicator() { if (mServerActive.isEmpty()) { hide(); } else { createIndicator(); show(); } } bool VacationScriptIndicatorWidget::hasVacationScriptActive() const { return !mServerActive.isEmpty(); }