diff --git a/ktnef/src/ktnefmain.cpp b/ktnef/src/ktnefmain.cpp index c34f5ecad..59de863d4 100644 --- a/ktnef/src/ktnefmain.cpp +++ b/ktnef/src/ktnefmain.cpp @@ -1,627 +1,627 @@ /* 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 "ktnefmain.h" #include "attachpropertydialog.h" #include "ktnefview.h" #include "messagepropertydialog.h" #include #include #include #include #include #include #include #include #include "ktnef_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KTNEFMain::KTNEFMain(QWidget *parent) : KXmlGuiWindow(parent) { setupActions(); setupStatusbar(); setupTNEF(); KConfigGroup config(KSharedConfig::openConfig(), "Settings"); mDefaultDir = config.readPathEntry("defaultdir", QStringLiteral("/tmp/")); config = KConfigGroup(KSharedConfig::openConfig(), "Recent Files"); mOpenRecentFileAction->loadEntries(config); mLastDir = mDefaultDir; // create personal temp extract dir QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/ktnef/")); resize(430, 350); setStandardToolBarMenuEnabled(true); createStandardStatusBarAction(); setupGUI(Keys | Save | Create, QStringLiteral("ktnefui.rc")); setAutoSaveSettings(); } KTNEFMain::~KTNEFMain() { delete mParser; cleanup(); } void KTNEFMain::setupActions() { KStandardAction::quit(this, &KTNEFMain::close, actionCollection()); QAction *action = KStandardAction::keyBindings(this, &KTNEFMain::slotConfigureKeys, actionCollection()); action->setWhatsThis( i18nc("@info:whatsthis", "You will be presented with a dialog where you can configure " "the application-wide shortcuts.")); KStandardAction::configureToolbars(this, &KTNEFMain::slotEditToolbars, actionCollection()); // File menu KStandardAction::open(this, &KTNEFMain::openFile, actionCollection()); mOpenRecentFileAction = KStandardAction::openRecent(this, &KTNEFMain::openRecentFile, actionCollection()); // Action menu QAction *openAction = actionCollection()->addAction(QStringLiteral("view_file")); openAction->setText(i18nc("@action:inmenu", "View")); openAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); connect(openAction, &QAction::triggered, this, &KTNEFMain::viewFile); QAction *openAsAction = actionCollection()->addAction(QStringLiteral("view_file_as")); openAsAction->setText(i18nc("@action:inmenu", "View With...")); connect(openAsAction, &QAction::triggered, this, &KTNEFMain::viewFileAs); QAction *extractAction = actionCollection()->addAction(QStringLiteral("extract_file")); extractAction->setText(i18nc("@action:inmenu", "Extract")); connect(extractAction, &QAction::triggered, this, &KTNEFMain::extractFile); QAction *extractToAction = actionCollection()->addAction(QStringLiteral("extract_file_to")); extractToAction->setText(i18nc("@action:inmenu", "Extract To...")); extractToAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract"))); connect(extractToAction, &QAction::triggered, this, &KTNEFMain::extractFileTo); QAction *extractAllToAction = actionCollection()->addAction(QStringLiteral("extract_all_files")); extractAllToAction->setText(i18nc("@action:inmenu", "Extract All To...")); extractAllToAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract"))); connect(extractAllToAction, &QAction::triggered, this, &KTNEFMain::extractAllFiles); QAction *filePropsAction = actionCollection()->addAction(QStringLiteral("properties_file")); filePropsAction->setText(i18nc("@action:inmenu", "Properties")); filePropsAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); connect(filePropsAction, &QAction::triggered, this, &KTNEFMain::propertiesFile); QAction *messPropsAction = actionCollection()->addAction(QStringLiteral("msg_properties")); messPropsAction->setText(i18nc("@action:inmenu", "Message Properties")); connect(messPropsAction, &QAction::triggered, this, &KTNEFMain::slotShowMessageProperties); QAction *messShowAction = actionCollection()->addAction(QStringLiteral("msg_text")); messShowAction->setText(i18nc("@action:inmenu", "Show Message Text")); messShowAction->setIcon(QIcon::fromTheme(QStringLiteral("document-preview-archive"))); connect(messShowAction, &QAction::triggered, this, &KTNEFMain::slotShowMessageText); QAction *messSaveAction = actionCollection()->addAction(QStringLiteral("msg_save")); messSaveAction->setText(i18nc("@action:inmenu", "Save Message Text As...")); messSaveAction->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); connect(messSaveAction, &QAction::triggered, this, &KTNEFMain::slotSaveMessageText); actionCollection()->action(QStringLiteral("view_file"))->setEnabled(false); actionCollection()->action(QStringLiteral("view_file_as"))->setEnabled(false); actionCollection()->action(QStringLiteral("extract_file"))->setEnabled(false); actionCollection()->action(QStringLiteral("extract_file_to"))->setEnabled(false); actionCollection()->action(QStringLiteral("extract_all_files"))->setEnabled(false); actionCollection()->action(QStringLiteral("properties_file"))->setEnabled(false); // Options menu QAction *defFolderAction = actionCollection()->addAction(QStringLiteral("options_default_dir")); defFolderAction->setText(i18nc("@action:inmenu", "Default Folder...")); defFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-open"))); connect(defFolderAction, &QAction::triggered, this, &KTNEFMain::optionDefaultDir); } void KTNEFMain::slotConfigureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KTNEFMain::setupStatusbar() { statusBar()->showMessage(i18nc("@info:status", "No file loaded")); } void KTNEFMain::setupTNEF() { mView = new KTNEFView(this); mView->setAllColumnsShowFocus(true); mParser = new KTNEFParser; setCentralWidget(mView); connect(mView, &QTreeWidget::itemSelectionChanged, this, &KTNEFMain::viewSelectionChanged); connect(mView, &QTreeWidget::itemDoubleClicked, this, &KTNEFMain::viewDoubleClicked); } void KTNEFMain::loadFile(const QString &filename) { mFilename = filename; setCaption(mFilename); if (!mParser->openFile(filename)) { mView->setAttachments(QList()); enableExtractAll(false); KMessageBox::error( this, i18nc("@info", "Unable to open file \"%1\".", filename)); } else { addRecentFile(QUrl::fromLocalFile(filename)); QList list = mParser->message()->attachmentList(); QString msg; msg = i18ncp("@info:status", "%1 attachment found", "%1 attachments found", list.count()); statusBar()->showMessage(msg); mView->setAttachments(list); enableExtractAll(!list.isEmpty()); enableSingleAction(false); } } void KTNEFMain::openFile() { const QString filename = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Open TNEF File")); if (!filename.isEmpty()) { loadFile(filename); } } void KTNEFMain::openRecentFile(const QUrl &url) { loadFile(url.path()); } void KTNEFMain::addRecentFile(const QUrl &url) { mOpenRecentFileAction->addUrl(url); KConfigGroup config(KSharedConfig::openConfig(), "Recent Files"); mOpenRecentFileAction->saveEntries(config); config.sync(); } void KTNEFMain::viewFile() { if (!mView->getSelection().isEmpty()) { KTNEFAttach *attach = mView->getSelection().at(0); QUrl url = QUrl::fromLocalFile(extractTemp(attach)); QString mimename(attach->mimeTag()); if (mimename.isEmpty() || mimename == QLatin1String("application/octet-stream")) { qCDebug(KTNEFAPPS_LOG) << "No mime type found in attachment object, trying to guess..."; QMimeDatabase db; db.mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension).name(); qCDebug(KTNEFAPPS_LOG) << "Detected mime type: " << mimename; } else { qCDebug(KTNEFAPPS_LOG) << "Mime type from attachment object: " << mimename; } KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; KRun::runUrl(url, mimename, this, flags); } else { KMessageBox::information( this, i18nc("@info", "There is no file selected. Please select a file an try again.")); } } QString KTNEFMain::extractTemp(KTNEFAttach *att) { QString dir = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/ktnef/"); mParser->extractFileTo(att->name(), dir); QString filename = att->fileName(); // falling back to internal TNEF attachement name if no filename is given for the attached file // this follows the logic of KTNEFParser::extractFileTo(...) if (filename.isEmpty()) { filename = att->name(); } dir.append(filename); return dir; } void KTNEFMain::viewFileAs() { if (!mView->getSelection().isEmpty()) { QList list; list.append(QUrl::fromLocalFile(extractTemp(mView->getSelection().at(0)))); if (!list.isEmpty()) { KRun::displayOpenWithDialog(list, this); } } else { KMessageBox::information( this, i18nc("@info", "There is no file selected. Please select a file an try again.")); } } void KTNEFMain::extractFile() { extractTo(mDefaultDir); } void KTNEFMain::extractFileTo() { const QString dir = QFileDialog::getExistingDirectory(this, QString(), mLastDir); if (!dir.isEmpty()) { extractTo(dir); mLastDir = dir; } } void KTNEFMain::extractAllFiles() { QString dir = QFileDialog::getExistingDirectory(this, QString(), mLastDir); if (!dir.isEmpty()) { mLastDir = dir; dir.append(QLatin1String("/")); QList list = mParser->message()->attachmentList(); QList::ConstIterator it; QList::ConstIterator end(list.constEnd()); for (it = list.constBegin(); it != end; ++it) { if (!mParser->extractFileTo((*it)->name(), dir)) { KMessageBox::error( this, i18nc("@info", "Unable to extract file \"%1\".", (*it)->name())); return; } } } } void KTNEFMain::propertiesFile() { KTNEFAttach *attach = mView->getSelection().at(0); QPointer dlg = new AttachPropertyDialog(this); dlg->setAttachment(attach); dlg->exec(); delete dlg; } void KTNEFMain::optionDefaultDir() { const QString dirname = QFileDialog::getExistingDirectory(this, QString(), mDefaultDir); if (!dirname.isEmpty()) { mDefaultDir = dirname; KConfigGroup config(KSharedConfig::openConfig(), "Settings"); config.writePathEntry("defaultdir", mDefaultDir); } } void KTNEFMain::viewSelectionChanged() { const QList list = mView->getSelection(); const int nbItem = list.count(); const bool on1 = (nbItem == 1); const bool on2 = (nbItem > 0); actionCollection()->action(QStringLiteral("view_file"))->setEnabled(on1); actionCollection()->action(QStringLiteral("view_file_as"))->setEnabled(on1); actionCollection()->action(QStringLiteral("properties_file"))->setEnabled(on1); actionCollection()->action(QStringLiteral("extract_file"))->setEnabled(on2); actionCollection()->action(QStringLiteral("extract_file_to"))->setEnabled(on2); } void KTNEFMain::enableExtractAll(bool on) { if (!on) { enableSingleAction(false); } actionCollection()->action(QStringLiteral("extract_all_files"))->setEnabled(on); } void KTNEFMain::enableSingleAction(bool on) { actionCollection()->action(QStringLiteral("extract_file"))->setEnabled(on); actionCollection()->action(QStringLiteral("extract_file_to"))->setEnabled(on); actionCollection()->action(QStringLiteral("view_file"))->setEnabled(on); actionCollection()->action(QStringLiteral("view_file_as"))->setEnabled(on); actionCollection()->action(QStringLiteral("properties_file"))->setEnabled(on); } void KTNEFMain::cleanup() { QDir d(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/ktnef/")); d.removeRecursively(); } void KTNEFMain::extractTo(const QString &dirname) { QString dir = dirname; if (dir.right(1) != QLatin1String("/")) { dir.append(QLatin1String("/")); } QList list = mView->getSelection(); QList::ConstIterator it; QList::ConstIterator end(list.constEnd()); for (it = list.constBegin(); it != end; ++it) { if (!mParser->extractFileTo((*it)->name(), dir)) { KMessageBox::error( this, i18nc("@info", "Unable to extract file \"%1\".", (*it)->name())); return; } } } void KTNEFMain::contextMenuEvent(QContextMenuEvent *event) { QList list = mView->getSelection(); if (!list.count()) { return; } QAction *prop = nullptr; QMenu menu(this); if (list.count() == 1) { createOpenWithMenu(&menu); menu.addSeparator(); } QAction *extract = menu.addAction(i18nc("@action:inmenu", "Extract")); QAction *extractTo = menu.addAction(QIcon::fromTheme(QStringLiteral("archive-extract")), i18nc("@action:inmenu", "Extract To...")); if (list.count() == 1) { menu.addSeparator(); prop = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties")); } QAction *a = menu.exec(event->globalPos(), nullptr); if (a) { if (a == extract) { extractFile(); } else if (a == extractTo) { extractFileTo(); } else if (a == prop) { propertiesFile(); } } } void KTNEFMain::viewDoubleClicked(QTreeWidgetItem *item) { if (item && item->isSelected()) { viewFile(); } } -void KTNEFMain::viewDragRequested(const QList &list) +void KTNEFMain::viewDragRequested(const QList &list) { QList urlList; QList::ConstIterator end(list.constEnd()); for (QList::ConstIterator it = list.constBegin(); it != end; ++it) { urlList << QUrl::fromLocalFile(extractTemp(*it)); } if (!list.isEmpty()) { QMimeData *mimeData = new QMimeData; mimeData->setUrls(urlList); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); } } void KTNEFMain::slotEditToolbars() { KConfigGroup grp = KSharedConfig::openConfig()->group("MainWindow"); saveMainWindowSettings(grp); QPointer dlg = new KEditToolBar(factory()); connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KTNEFMain::slotNewToolbarConfig); dlg->exec(); delete dlg; } void KTNEFMain::slotNewToolbarConfig() { createGUI(QStringLiteral("ktnefui.rc")); applyMainWindowSettings(KSharedConfig::openConfig()->group("MainWindow")); } void KTNEFMain::slotShowMessageProperties() { QPointer dlg = new MessagePropertyDialog(this, mParser->message()); dlg->exec(); delete dlg; } void KTNEFMain::slotShowMessageText() { if (!mParser->message()) { return; } QString rtf = mParser->message()->rtfString(); if (!rtf.isEmpty()) { QTemporaryFile *tmpFile = new QTemporaryFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/ktnef/") + QLatin1String("ktnef_XXXXXX.rtf")); tmpFile->setAutoRemove(false); tmpFile->open(); tmpFile->setPermissions(QFile::ReadUser); tmpFile->write(rtf.toLocal8Bit()); tmpFile->close(); KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; KRun::runUrl(QUrl::fromLocalFile(tmpFile->fileName()), QStringLiteral("text/rtf"), this, flags); delete tmpFile; } else { KMessageBox::error( this, i18nc("@info", "The message does not contain any Rich Text data.")); } } void KTNEFMain::slotSaveMessageText() { if (!mParser->message()) { return; } QString rtf = mParser->message()->rtfString(); QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), QString()); if (!filename.isEmpty()) { QFile f(filename); if (f.open(QIODevice::WriteOnly)) { QTextStream t(&f); t << rtf; } else { KMessageBox::error( this, i18nc("@info", "Unable to open file \"%1\" for writing, check file permissions.", filename)); } } } void KTNEFMain::openWith(const KService::Ptr &offer) { if (!mView->getSelection().isEmpty()) { KTNEFAttach *attach = mView->getSelection().at(0); QUrl url = QUrl::fromLocalFile(QLatin1String("file:") + extractTemp(attach)); QList lst; lst.append(url); if (offer) { KRun::runService(*offer, lst, this, false); } else { KRun::displayOpenWithDialog(lst, this, false); } } } QAction *KTNEFMain::createAppAction(const KService::Ptr &service, bool singleOffer, QActionGroup *actionGroup, QObject *parent) { QString actionName(service->name().replace(QLatin1Char('&'), QStringLiteral("&&"))); if (singleOffer) { actionName = i18n("Open &with %1", actionName); } else { actionName = i18nc("@item:inmenu Open With, %1 is application name", "%1", actionName); } QAction *act = new QAction(parent); act->setIcon(QIcon::fromTheme(service->icon())); act->setText(actionName); actionGroup->addAction(act); act->setData(QVariant::fromValue(service)); return act; } void KTNEFMain::createOpenWithMenu(QMenu *topMenu) { if (mView->getSelection().isEmpty()) { return; } KTNEFAttach *attach = mView->getSelection().at(0); QString mimename(attach->mimeTag()); const KService::List offers = KFileItemActions::associatedApplications(QStringList() << mimename, QString()); if (!offers.isEmpty()) { QMenu *menu = topMenu; QActionGroup *actionGroup = new QActionGroup(menu); connect(actionGroup, &QActionGroup::triggered, this, &KTNEFMain::slotOpenWithAction); if (offers.count() > 1) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest topMenu->addMenu(menu); } KService::List::ConstIterator it = offers.constBegin(); KService::List::ConstIterator end = offers.constEnd(); for (; it != end; ++it) { QAction *act = createAppAction(*it, // no submenu -> prefix single offer menu == topMenu, actionGroup, menu); menu->addAction(act); } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } QAction *openWithAct = new QAction(menu); openWithAct->setText(openWithActionName); connect(openWithAct, &QAction::triggered, this, &KTNEFMain::viewFileAs); menu->addAction(openWithAct); } else { // no app offers -> Open With... QAction *act = new QAction(topMenu); act->setText(i18nc("@title:menu", "&Open With...")); connect(act, &QAction::triggered, this, &KTNEFMain::viewFileAs); topMenu->addAction(act); } } void KTNEFMain::slotOpenWithAction(QAction *act) { KService::Ptr app = act->data().value(); openWith(app); } diff --git a/ktnef/src/ktnefmain.h b/ktnef/src/ktnefmain.h index ac3265c42..29f9285fa 100644 --- a/ktnef/src/ktnefmain.h +++ b/ktnef/src/ktnefmain.h @@ -1,97 +1,97 @@ /* 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 */ #ifndef KTNEFMAIN_H #define KTNEFMAIN_H #include #include class QActionGroup; class QAction; class QContextMenuEvent; class QTreeWidgetItem; class KRecentFilesAction; class QUrl; namespace KTnef { class KTNEFParser; class KTNEFAttach; } using namespace KTnef; class KTNEFView; class KTNEFMain : public KXmlGuiWindow { Q_OBJECT public: explicit KTNEFMain(QWidget *parent = nullptr); ~KTNEFMain(); void loadFile(const QString &filename); protected: void contextMenuEvent(QContextMenuEvent *event) override; protected Q_SLOTS: void openFile(); void viewFile(); void viewFileAs(); void extractFile(); void extractFileTo(); void propertiesFile(); void optionDefaultDir(); void extractAllFiles(); void slotEditToolbars(); void slotNewToolbarConfig(); void slotShowMessageProperties(); void slotShowMessageText(); void slotSaveMessageText(); void viewSelectionChanged(); void viewDoubleClicked(QTreeWidgetItem *); - void viewDragRequested(const QList &list); + void viewDragRequested(const QList &list); void slotConfigureKeys(); void openRecentFile(const QUrl &); private: void slotOpenWithAction(QAction *act); void addRecentFile(const QUrl &url); void setupStatusbar(); void setupActions(); void setupTNEF(); void enableExtractAll(bool on = true); void enableSingleAction(bool on = true); void cleanup(); void extractTo(const QString &dirname); QString extractTemp(KTNEFAttach *att); void openWith(const KService::Ptr &offer); void createOpenWithMenu(QMenu *topMenu); QAction *createAppAction(const KService::Ptr &service, bool singleOffer, QActionGroup *actionGroup, QObject *parent); private: QString mFilename; QString mDefaultDir; QString mLastDir; KTNEFView *mView = nullptr; KTNEFParser *mParser = nullptr; KRecentFilesAction *mOpenRecentFileAction = nullptr; }; Q_DECLARE_METATYPE(KService::Ptr) #endif diff --git a/ktnef/src/ktnefview.h b/ktnef/src/ktnefview.h index 95bd383a2..fc70bb873 100644 --- a/ktnef/src/ktnefview.h +++ b/ktnef/src/ktnefview.h @@ -1,50 +1,50 @@ /* 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 */ #ifndef KTNEFVIEW_H #define KTNEFVIEW_H #include namespace KTnef { class KTNEFAttach; } using namespace KTnef; class KTNEFView : public QTreeWidget { Q_OBJECT public: explicit KTNEFView(QWidget *parent = nullptr); ~KTNEFView() override; void setAttachments(const QList &list); QList getSelection(); Q_SIGNALS: - void dragRequested(const QList &list); + void dragRequested(const QList &list); protected: void resizeEvent(QResizeEvent *e) override; void startDrag(Qt::DropActions dropAction) override; private: void adjustColumnWidth(); QList mAttachments; }; #endif diff --git a/src/configuredialog/configagentdelegate.cpp b/src/configuredialog/configagentdelegate.cpp index dd662e6e7..e481e4b73 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()).arg(name) + "").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); 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/kmcommands.cpp b/src/kmcommands.cpp index 7655d6e91..09304a79d 100644 --- a/src/kmcommands.cpp +++ b/src/kmcommands.cpp @@ -1,1866 +1,1866 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (C) 2013-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 */ // // This file implements various "command" classes. These command classes // are based on the command design pattern. // // Historically various operations were implemented as slots of KMMainWin. // This proved inadequate as KMail has multiple top level windows // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may // benefit from using these operations. It is desirable that these // classes can operate without depending on or altering the state of // a KMMainWin, in fact it is possible no KMMainWin object even exists. // // Now these operations have been rewritten as KMCommand based classes, // making them independent of KMMainWin. // // The base command class KMCommand is async, which is a difference // from the conventional command pattern. As normal derived classes implement // the execute method, but client classes call start() instead of // calling execute() directly. start() initiates async operations, // and on completion of these operations calls execute() and then deletes // the command. (So the client must not construct commands on the stack). // // The type of async operation supported by KMCommand is retrieval // of messages from an IMAP server. #include "kmcommands.h" #include "widgets/collectionpane.h" #include "kmreadermainwin.h" #include "secondarywindow.h" #include "util.h" #include "settings/kmailsettings.h" #include "kmail_debug.h" #include "job/createreplymessagejob.h" #include "job/createforwardmessagejob.h" #include "editor/composer.h" #include "kmmainwidget.h" #include "undostack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_CURSOR #include #endif #include #include #include #include #include #include #include #include // KIO headers #include #include #include #include #include #include #include #include #include #include using KMail::SecondaryWindow; using MailTransport::TransportManager; using MessageComposer::MessageFactoryNG; using KPIM::ProgressManager; using KPIM::ProgressItem; using namespace KMime; using namespace MailCommon; /// Helper to sanely show an error message for a job static void showJobError(KJob *job) { assert(job); // we can be called from the KJob::kill, where we are no longer a KIO::Job // so better safe than sorry KIO::Job *kiojob = qobject_cast(job); if (kiojob && kiojob->uiDelegate()) { kiojob->uiDelegate()->showErrorMessage(); } else { qCWarning(KMAIL_LOG) << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString(); } } KMCommand::KMCommand(QWidget *parent) : mCountMsgs(0) , mResult(Undefined) , mDeletesItself(false) , mEmitsCompletedItself(false) , mParent(parent) { } KMCommand::KMCommand(QWidget *parent, const Akonadi::Item &msg) : mCountMsgs(0) , mResult(Undefined) , mDeletesItself(false) , mEmitsCompletedItself(false) , mParent(parent) { if (msg.isValid() || msg.hasPayload()) { mMsgList.append(msg); } } KMCommand::KMCommand(QWidget *parent, const Akonadi::Item::List &msgList) : mCountMsgs(0) , mResult(Undefined) , mDeletesItself(false) , mEmitsCompletedItself(false) , mParent(parent) { mMsgList = msgList; } KMCommand::~KMCommand() { } KMCommand::Result KMCommand::result() const { if (mResult == Undefined) { qCDebug(KMAIL_LOG) << "mResult is Undefined"; } return mResult; } const Akonadi::Item::List KMCommand::retrievedMsgs() const { return mRetrievedMsgs; } Akonadi::Item KMCommand::retrievedMessage() const { if (mRetrievedMsgs.isEmpty()) { return Akonadi::Item(); } return *(mRetrievedMsgs.begin()); } QWidget *KMCommand::parentWidget() const { return mParent; } bool KMCommand::deletesItself() const { return mDeletesItself; } void KMCommand::setDeletesItself(bool deletesItself) { mDeletesItself = deletesItself; } bool KMCommand::emitsCompletedItself() const { return mEmitsCompletedItself; } void KMCommand::setEmitsCompletedItself(bool emitsCompletedItself) { mEmitsCompletedItself = emitsCompletedItself; } void KMCommand::setResult(KMCommand::Result result) { mResult = result; } int KMCommand::mCountJobs = 0; void KMCommand::start() { connect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer); if (mMsgList.isEmpty()) { Q_EMIT messagesTransfered(OK); return; } // Special case of operating on message that isn't in a folder const Akonadi::Item mb = mMsgList.first(); if ((mMsgList.count() == 1) && MessageComposer::Util::isStandaloneMessage(mb)) { mRetrievedMsgs.append(mMsgList.takeFirst()); Q_EMIT messagesTransfered(OK); return; } // we can only retrieve items with a valid id for (const Akonadi::Item &item : qAsConst(mMsgList)) { if (!item.isValid()) { Q_EMIT messagesTransfered(Failed); return; } } // transfer the selected messages first transferSelectedMsgs(); } void KMCommand::slotPostTransfer(KMCommand::Result result) { disconnect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer); if (result == OK) { result = execute(); } mResult = result; if (!emitsCompletedItself()) { Q_EMIT completed(this); } if (!deletesItself()) { deleteLater(); } } Akonadi::ItemFetchJob *KMCommand::createFetchJob(const Akonadi::Item::List &items) { return new Akonadi::ItemFetchJob(items, this); } void KMCommand::transferSelectedMsgs() { // make sure no other transfer is active if (KMCommand::mCountJobs > 0) { Q_EMIT messagesTransfered(Failed); return; } bool complete = true; KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); mCountMsgs = mMsgList.count(); uint totalSize = 0; // the QProgressDialog for the user-feedback. Only enable it if it's needed. // For some commands like KMSetStatusCommand it's not needed. Note, that // for some reason the QProgressDialog eats the MouseReleaseEvent (if a // command is executed after the MousePressEvent), cf. bug #71761. if (mCountMsgs > 0) { mProgressDialog = new QProgressDialog(mParent); mProgressDialog.data()->setWindowTitle(i18n("Please wait")); mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count())); mProgressDialog.data()->setModal(true); mProgressDialog.data()->setMinimumDuration(1000); } // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever if (!mFetchScope.isEmpty()) { complete = false; ++KMCommand::mCountJobs; Akonadi::ItemFetchJob *fetch = createFetchJob(mMsgList); mFetchScope.fetchAttribute< MailCommon::MDNStateAttribute >(); fetch->setFetchScope(mFetchScope); connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, &KMCommand::slotMsgTransfered); connect(fetch, &Akonadi::ItemFetchJob::result, this, &KMCommand::slotJobFinished); } else { // no need to fetch anything if (!mMsgList.isEmpty()) { mRetrievedMsgs = mMsgList; } } if (complete) { delete mProgressDialog.data(); mProgressDialog.clear(); Q_EMIT messagesTransfered(OK); } else { // wait for the transfer and tell the progressBar the necessary steps if (mProgressDialog.data()) { connect(mProgressDialog.data(), &QProgressDialog::canceled, this, &KMCommand::slotTransferCancelled); mProgressDialog.data()->setMaximum(totalSize); } } } void KMCommand::slotMsgTransfered(const Akonadi::Item::List &msgs) { if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) { Q_EMIT messagesTransfered(Canceled); return; } // save the complete messages mRetrievedMsgs.append(msgs); } void KMCommand::slotJobFinished() { // the job is finished (with / without error) KMCommand::mCountJobs--; if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) { return; } if (mCountMsgs > mRetrievedMsgs.count()) { // the message wasn't retrieved before => error if (mProgressDialog.data()) { mProgressDialog.data()->hide(); } slotTransferCancelled(); return; } // update the progressbar if (mProgressDialog.data()) { mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", KMCommand::mCountJobs)); } if (KMCommand::mCountJobs == 0) { // all done delete mProgressDialog.data(); mProgressDialog.clear(); Q_EMIT messagesTransfered(OK); } } void KMCommand::slotTransferCancelled() { KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); Q_EMIT messagesTransfered(Canceled); } KMMailtoComposeCommand::KMMailtoComposeCommand(const QUrl &url, const Akonadi::Item &msg) : mUrl(url) , mMessage(msg) { } KMCommand::Result KMMailtoComposeCommand::execute() { KMime::Message::Ptr msg(new KMime::Message); uint id = 0; if (mMessage.isValid() && mMessage.parentCollection().isValid()) { QSharedPointer fd = FolderSettings::forCollection(mMessage.parentCollection(), false); id = fd->identity(); } MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id); msg->contentType()->setCharset("utf-8"); msg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8"); KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id); win->setFocusToSubject(); win->show(); return OK; } KMMailtoReplyCommand::KMMailtoReplyCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg, const QString &selection) : KMCommand(parent, msg) , mUrl(url) , mSelection(selection) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMMailtoReplyCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } CreateReplyMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mSelection = mSelection; settings.mUrl = mUrl; settings.m_replyStrategy = MessageComposer::ReplyNone; CreateReplyMessageJob *job = new CreateReplyMessageJob; job->setSettings(settings); job->start(); return OK; } KMMailtoForwardCommand::KMMailtoForwardCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg) : KMCommand(parent, msg) , mUrl(url) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMMailtoForwardCommand::execute() { //TODO : consider factoring createForward into this method. Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } CreateForwardMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mUrl = mUrl; CreateForwardMessageJob *job = new CreateForwardMessageJob; job->setSettings(settings); job->start(); return OK; } KMAddBookmarksCommand::KMAddBookmarksCommand(const QUrl &url, QWidget *parent) : KMCommand(parent) , mUrl(url) { } KMCommand::Result KMAddBookmarksCommand::execute() { const QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/konqueror/bookmarks.xml"); QFileInfo fileInfo(filename); QDir().mkpath(fileInfo.absolutePath()); KBookmarkManager *bookManager = KBookmarkManager::managerForFile(filename, QStringLiteral("konqueror")); KBookmarkGroup group = bookManager->root(); group.addBookmark(mUrl.path(), QUrl(mUrl), QString()); if (bookManager->save()) { bookManager->emitChanged(group); } return OK; } KMUrlSaveCommand::KMUrlSaveCommand(const QUrl &url, QWidget *parent) : KMCommand(parent) , mUrl(url) { } KMCommand::Result KMUrlSaveCommand::execute() { if (mUrl.isEmpty()) { return OK; } QString recentDirClass; QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///OpenMessage")), recentDirClass); startUrl.setPath(startUrl.path() + QLatin1Char('/') + mUrl.fileName()); const QUrl saveUrl = QFileDialog::getSaveFileUrl(parentWidget(), i18n("Save To File"), startUrl); if (saveUrl.isEmpty()) { return Canceled; } if (!recentDirClass.isEmpty()) { KRecentDirs::add(recentDirClass, saveUrl.path()); } bool fileExists = false; if (saveUrl.isLocalFile()) { fileExists = QFile::exists(saveUrl.toLocalFile()); } else { auto job = KIO::stat(saveUrl, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(job, parentWidget()); fileExists = job->exec(); } if (fileExists) { if (KMessageBox::warningContinueCancel(nullptr, xi18nc("@info", "File %1 exists.Do you want to replace it?", saveUrl.toDisplayString()), i18n("Save to File"), KGuiItem(i18n("&Replace"))) != KMessageBox::Continue) { return Canceled; } } KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite); connect(job, &KIO::Job::result, this, &KMUrlSaveCommand::slotUrlSaveResult); setEmitsCompletedItself(true); return OK; } void KMUrlSaveCommand::slotUrlSaveResult(KJob *job) { if (job->error()) { showJobError(job); setResult(Failed); } else { setResult(OK); } Q_EMIT completed(this); } KMEditMessageCommand::KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg) : KMCommand(parent) , mMessage(msg) { } KMCommand::Result KMEditMessageCommand::execute() { if (!mMessage) { return Failed; } KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage); win->setMessage(mMessage, lastSign, lastEncrypt, false, true); win->show(); win->setModified(true); return OK; } KMEditItemCommand::KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource) : KMCommand(parent, msg) , mDeleteFromSource(deleteFromSource) { fetchScope().fetchFullPayload(true); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMEditItemCommand::~KMEditItemCommand() { } KMCommand::Result KMEditItemCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.parentCollection().isValid()) { return Failed; } KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } if (mDeleteFromSource) { setDeletesItself(true); Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(item); connect(job, &KIO::Job::result, this, &KMEditItemCommand::slotDeleteItem); } KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); win->setMessage(msg, lastSign, lastEncrypt, false, true); win->setFolder(item.parentCollection()); const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if (transportAttribute) { win->setCurrentTransport(transportAttribute->transportId()); } else { int transportId = -1; if (auto hrd = msg->headerByType("X-KMail-Transport")) { transportId = hrd->asUnicodeString().toInt(); } if (transportId != -1) { win->setCurrentTransport(transportId); } } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) { win->setFcc(QString::number(sentAttribute->moveToCollection().id())); } win->show(); if (mDeleteFromSource) { win->setModified(true); } return OK; } void KMEditItemCommand::slotDeleteItem(KJob *job) { if (job->error()) { showJobError(job); setResult(Failed); } else { setResult(OK); } Q_EMIT completed(this); deleteLater(); } KMUseTemplateCommand::KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMUseTemplateCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.parentCollection().isValid() || !CommonKernel->folderIsTemplates(item.parentCollection()) ) { return Failed; } KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } KMime::Message::Ptr newMsg(new KMime::Message); newMsg->setContent(msg->encodedContent()); newMsg->parse(); // these fields need to be regenerated for the new message newMsg->removeHeader(); newMsg->removeHeader(); KMail::Composer *win = KMail::makeComposer(); win->setMessage(newMsg, false, false, false, true); win->show(); return OK; } KMSaveMsgCommand::KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList) : KMCommand(parent, msgList) { if (msgList.empty()) { return; } fetchScope().fetchFullPayload(true); // ### unless we call the corresponding KMCommand ctor, this has no effect } KMCommand::Result KMSaveMsgCommand::execute() { if (!MessageViewer::Util::saveMessageInMbox(retrievedMsgs(), parentWidget())) { return Failed; } return OK; } //----------------------------------------------------------------------------- KMOpenMsgCommand::KMOpenMsgCommand(QWidget *parent, const QUrl &url, const QString &encoding, KMMainWidget *main) : KMCommand(parent) , mUrl(url) , mJob(nullptr) , mEncoding(encoding) , mMainWidget(main) { qCDebug(KMAIL_LOG) << "url :" << url; } KMCommand::Result KMOpenMsgCommand::execute() { if (mUrl.isEmpty()) { mUrl = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Open Message"), QUrl(), QStringLiteral("%1 (*.mbox)").arg(i18n("Message")) ); } if (mUrl.isEmpty()) { return Canceled; } if (mMainWidget) { mMainWidget->addRecentFile(mUrl); } setDeletesItself(true); mJob = KIO::get(mUrl, KIO::NoReload, KIO::HideProgressInfo); connect(mJob, &KIO::TransferJob::data, this, &KMOpenMsgCommand::slotDataArrived); connect(mJob, &KJob::result, this, &KMOpenMsgCommand::slotResult); setEmitsCompletedItself(true); return OK; } void KMOpenMsgCommand::slotDataArrived(KIO::Job *, const QByteArray &data) { if (data.isEmpty()) { return; } mMsgString.append(QString::fromLatin1(data.data())); } void KMOpenMsgCommand::doesNotContainMessage() { KMessageBox::sorry(parentWidget(), i18n("The file does not contain a message.")); setResult(Failed); Q_EMIT completed(this); // Emulate closing of a secondary window so that KMail exits in case it // was started with the --view command line option. Otherwise an // invisible KMail would keep running. SecondaryWindow *win = new SecondaryWindow(); win->close(); win->deleteLater(); deleteLater(); } void KMOpenMsgCommand::slotResult(KJob *job) { if (job->error()) { // handle errors showJobError(job); setResult(Failed); } else { if (mMsgString.isEmpty()) { qCDebug(KMAIL_LOG) << " Message not found. There is a problem"; doesNotContainMessage(); return; } int startOfMessage = 0; if (mMsgString.startsWith(QLatin1String("From "))) { startOfMessage = mMsgString.indexOf(QLatin1Char('\n')); if (startOfMessage == -1) { doesNotContainMessage(); return; } startOfMessage += 1; // the message starts after the '\n' } // check for multiple messages in the file bool multipleMessages = true; int endOfMessage = mMsgString.indexOf(QLatin1String("\nFrom ")); if (endOfMessage == -1) { endOfMessage = mMsgString.length(); multipleMessages = false; } KMime::Message *msg = new KMime::Message; msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage).toUtf8())); msg->parse(); if (!msg->hasContent()) { delete msg; msg = nullptr; doesNotContainMessage(); return; } KMReaderMainWin *win = new KMReaderMainWin(); KMime::Message::Ptr mMsg(msg); win->showMessage(mEncoding, mMsg); win->show(); if (multipleMessages) { KMessageBox::information(win, i18n("The file contains multiple messages. " "Only the first message is shown.")); } setResult(OK); } Q_EMIT completed(this); deleteLater(); } //----------------------------------------------------------------------------- KMReplyCommand::KMReplyCommand(QWidget *parent, const Akonadi::Item &msg, MessageComposer::ReplyStrategy replyStrategy, const QString &selection, bool noquote, const QString &templateName) : KMCommand(parent, msg) , mSelection(selection) , mTemplate(templateName) , m_replyStrategy(replyStrategy) , mNoQuote(noquote) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMReplyCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } CreateReplyMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mSelection = mSelection; settings.m_replyStrategy = m_replyStrategy; settings.mTemplate = mTemplate; settings.mNoQuote = mNoQuote; CreateReplyMessageJob *job = new CreateReplyMessageJob; job->setSettings(settings); job->start(); return OK; } KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, const QString &templateName, const QString &selection) : KMCommand(parent, msgList) , mIdentity(identity) , mTemplate(templateName) , mSelection(selection) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, const QString &templateName, const QString &selection) : KMCommand(parent, msg) , mIdentity(identity) , mTemplate(templateName) , mSelection(selection) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item &item) { KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif CreateForwardMessageJobSettings settings; settings.mItem = item; settings.mMsg = msg; settings.mIdentity = mIdentity; settings.mTemplate = mTemplate; settings.mSelection = mSelection; CreateForwardMessageJob *job = new CreateForwardMessageJob; job->setSettings(settings); job->start(); return OK; } KMCommand::Result KMForwardCommand::execute() { Akonadi::Item::List msgList = retrievedMsgs(); if (msgList.count() >= 2) { // ask if they want a mime digest forward int answer = KMessageBox::questionYesNoCancel( parentWidget(), i18n("Do you want to forward the selected messages as " "attachments in one message (as a MIME digest) or as " "individual messages?"), QString(), KGuiItem(i18n("Send As Digest")), KGuiItem(i18n("Send Individually"))); if (answer == KMessageBox::Yes) { Akonadi::Item firstItem(msgList.first()); MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), CommonKernel->collectionFromId(firstItem.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem)); QPair< KMime::Message::Ptr, KMime::Content * > fwdMsg = factory.createForwardDigestMIME(msgList); KMail::Composer *win = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity); win->addAttach(fwdMsg.second); win->show(); delete fwdMsg.second; return OK; } else if (answer == KMessageBox::No) { // NO MIME DIGEST, Multiple forward Akonadi::Item::List::const_iterator it; Akonadi::Item::List::const_iterator end(msgList.constEnd()); for (it = msgList.constBegin(); it != end; ++it) { if (createComposer(*it) == Failed) { return Failed; } } return OK; } else { // user cancelled return OK; } } // forward a single message at most. Akonadi::Item item = msgList.first(); if (createComposer(item) == Failed) { return Failed; } return OK; } KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, KMail::Composer *win) : KMCommand(parent, msgList) , mIdentity(identity) , mWin(QPointer(win)) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, KMail::Composer *win) : KMCommand(parent, msg) , mIdentity(identity) , mWin(QPointer< KMail::Composer >(win)) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMForwardAttachedCommand::execute() { Akonadi::Item::List msgList = retrievedMsgs(); Akonadi::Item firstItem(msgList.first()); MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), CommonKernel->collectionFromId(firstItem.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem)); QPair< KMime::Message::Ptr, QList< KMime::Content * > > fwdMsg = factory.createAttachedForward(msgList); if (!mWin) { mWin = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity); } for (KMime::Content *attach : qAsConst(fwdMsg.second)) { mWin->addAttach(attach); delete attach; } mWin->show(); return OK; } KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item::List &msgList) : KMCommand(parent, msgList) { fetchScope().fetchFullPayload(true); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { fetchScope().fetchFullPayload(true); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMRedirectCommand::execute() { const MailCommon::RedirectDialog::SendMode sendMode = MessageComposer::MessageComposerSettings::self()->sendImmediate() ? MailCommon::RedirectDialog::SendNow : MailCommon::RedirectDialog::SendLater; QScopedPointer dlg(new MailCommon::RedirectDialog(sendMode, parentWidget())); dlg->setObjectName(QStringLiteral("redirect")); if (dlg->exec() == QDialog::Rejected || !dlg) { return Failed; } if (!TransportManager::self()->showTransportCreationDialog(parentWidget(), TransportManager::IfNoTransportExists)) { return Failed; } //TODO use sendlateragent here too. const MessageComposer::MessageSender::SendMethod method = (dlg->sendMode() == MailCommon::RedirectDialog::SendNow) ? MessageComposer::MessageSender::SendImmediate : MessageComposer::MessageSender::SendLater; const int identity = dlg->identity(); int transportId = dlg->transportId(); const QString to = dlg->to(); const QString cc = dlg->cc(); const QString bcc = dlg->bcc(); const Akonadi::Item::List lstItems = retrievedMsgs(); for (const Akonadi::Item &item : lstItems) { const KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(item)); if (transportId == -1) { const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if (transportAttribute) { transportId = transportAttribute->transportId(); const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId); if (!transport) { transportId = -1; } } } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); QString fcc; if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) { fcc = QString::number(sentAttribute->moveToCollection().id()); } const KMime::Message::Ptr newMsg = factory.createRedirect(to, cc, bcc, transportId, fcc, identity); if (!newMsg) { return Failed; } MessageStatus status; status.setStatusFromFlags(item.flags()); if (!status.isRead()) { FilterAction::sendMDN(item, KMime::MDN::Dispatched); } if (!kmkernel->msgSender()->send(newMsg, method)) { qCDebug(KMAIL_LOG) << "KMRedirectCommand: could not redirect message (sending failed)"; return Failed; // error: couldn't send } } return OK; } KMPrintCommand::KMPrintCommand(QWidget *parent, const KMPrintCommandInfo &commandInfo) : KMCommand(parent, commandInfo.mMsg) , mPrintCommandInfo(commandInfo) { fetchScope().fetchFullPayload(true); if (MessageCore::MessageCoreSettings::useDefaultFonts()) { mPrintCommandInfo.mOverrideFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); } else { mPrintCommandInfo.mOverrideFont = MessageViewer::MessageViewerSettings::self()->printFont(); } } KMCommand::Result KMPrintCommand::execute() { KMReaderWin *printerWin = new KMReaderWin(nullptr, kmkernel->mainWin(), nullptr); printerWin->setPrinting(true); printerWin->readConfig(); printerWin->setPrintElementBackground(MessageViewer::MessageViewerSettings::self()->printBackgroundColorImages()); if (mPrintCommandInfo.mHeaderStylePlugin) { printerWin->viewer()->setPluginName(mPrintCommandInfo.mHeaderStylePlugin->name()); } printerWin->setDisplayFormatMessageOverwrite(mPrintCommandInfo.mFormat); printerWin->setHtmlLoadExtOverride(mPrintCommandInfo.mHtmlLoadExtOverride); printerWin->setUseFixedFont(mPrintCommandInfo.mUseFixedFont); printerWin->setOverrideEncoding(mPrintCommandInfo.mEncoding); printerWin->cssHelper()->setPrintFont(mPrintCommandInfo.mOverrideFont); printerWin->setDecryptMessageOverwrite(true); if (mPrintCommandInfo.mAttachmentStrategy) { printerWin->setAttachmentStrategy(mPrintCommandInfo.mAttachmentStrategy); } printerWin->viewer()->setShowSignatureDetails(mPrintCommandInfo.mShowSignatureDetails); printerWin->viewer()->setShowEncryptionDetails(mPrintCommandInfo.mShowEncryptionDetails); if (mPrintCommandInfo.mPrintPreview) { printerWin->viewer()->printPreviewMessage(retrievedMessage()); } else { printerWin->viewer()->printMessage(retrievedMessage()); } return OK; } KMSetStatusCommand::KMSetStatusCommand(const MessageStatus &status, const Akonadi::Item::List &items, bool invert) : KMCommand(nullptr, items) , mStatus(status) , mInvertMark(invert) { setDeletesItself(true); } KMCommand::Result KMSetStatusCommand::execute() { bool parentStatus = false; // Toggle actions on threads toggle the whole thread // depending on the state of the parent. if (mInvertMark) { const Akonadi::Item first = retrievedMsgs().first(); MessageStatus pStatus; pStatus.setStatusFromFlags(first.flags()); if (pStatus & mStatus) { parentStatus = true; } else { parentStatus = false; } } Akonadi::Item::List itemsToModify; const Akonadi::Item::List lstItems = retrievedMsgs(); for (const Akonadi::Item &it : lstItems) { if (mInvertMark) { //qCDebug(KMAIL_LOG)<<" item ::"<disableRevisionCheck(); modifyJob->setIgnorePayload(true); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetStatusCommand::slotModifyItemDone); } return OK; } void KMSetStatusCommand::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMSetTagCommand::KMSetTagCommand(const Akonadi::Tag::List &tags, const Akonadi::Item::List &item, SetTagMode mode) : mTags(tags) , mItem(item) , mMode(mode) { setDeletesItself(true); } KMCommand::Result KMSetTagCommand::execute() { for (const Akonadi::Tag &tag : qAsConst(mTags)) { if (!tag.isValid()) { Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(tag, this); connect(createJob, &Akonadi::TagCreateJob::result, this, &KMSetTagCommand::slotModifyItemDone); } else { mCreatedTags << tag; } } if (mCreatedTags.size() == mTags.size()) { setTags(); } else { deleteLater(); } return OK; } void KMSetTagCommand::setTags() { Akonadi::Item::List itemsToModify; itemsToModify.reserve(mItem.count()); for (const Akonadi::Item &i : qAsConst(mItem)) { Akonadi::Item item(i); if (mMode == CleanExistingAndAddNew) { //WorkAround. ClearTags doesn't work. const Akonadi::Tag::List lstTags = item.tags(); for (const Akonadi::Tag &tag : lstTags) { item.clearTag(tag); } //item.clearTags(); } if (mMode == KMSetTagCommand::Toggle) { for (const Akonadi::Tag &tag : qAsConst(mCreatedTags)) { if (item.hasTag(tag)) { item.clearTag(tag); } else { item.setTag(tag); } } } else { if (!mCreatedTags.isEmpty()) { item.setTags(mCreatedTags); } } itemsToModify << item; } Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this); modifyJob->disableRevisionCheck(); modifyJob->setIgnorePayload(true); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetTagCommand::slotModifyItemDone); if (!mCreatedTags.isEmpty()) { KConfigGroup tag(KMKernel::self()->config(), "MessageListView"); const QString oldTagList = tag.readEntry("TagSelected"); QStringList lst = oldTagList.split(QLatin1Char(',')); for (const Akonadi::Tag &tag : qAsConst(mCreatedTags)) { const QString url = tag.url().url(); if (!lst.contains(url)) { lst.append(url); } } tag.writeEntry("TagSelected", lst); KMKernel::self()->updatePaneTagComboBox(); } } void KMSetTagCommand::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMFilterActionCommand::KMFilterActionCommand(QWidget *parent, const QVector &msgListId, const QString &filterId) : KMCommand(parent) , mMsgListId(msgListId) , mFilterId(filterId) { } KMCommand::Result KMFilterActionCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif int msgCount = 0; const int msgCountToFilter = mMsgListId.count(); ProgressItem *progressItem = ProgressManager::createProgressItem( QLatin1String("filter") + ProgressManager::getUniqueID(), i18n("Filtering messages"), QString(), true, KPIM::ProgressItem::Unknown); progressItem->setTotalItems(msgCountToFilter); for (const qlonglong &id : qAsConst(mMsgListId)) { int diff = msgCountToFilter - ++msgCount; if (diff < 10 || !(msgCount % 10) || msgCount <= 10) { progressItem->updateProgress(); const QString statusMsg = i18n("Filtering message %1 of %2", msgCount, msgCountToFilter); KPIM::BroadcastStatus::instance()->setStatusMsg(statusMsg); qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 50); } MailCommon::FilterManager::instance()->filter(Akonadi::Item(id), mFilterId, QString()); progressItem->incCompletedItems(); } progressItem->setComplete(); progressItem = nullptr; return OK; } KMMetaFilterActionCommand::KMMetaFilterActionCommand(const QString &filterId, KMMainWidget *main) : QObject(main) , mFilterId(filterId) , mMainWidget(main) { } void KMMetaFilterActionCommand::start() { KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId(), mFilterId); filterCommand->start(); } KMMailingListFilterCommand::KMMailingListFilterCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { } KMCommand::Result KMMailingListFilterCommand::execute() { QByteArray name; QString value; Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } if (!MailingList::name(msg, name, value).isEmpty()) { FilterIf->openFilterDialog(false); FilterIf->createFilter(name, value); return OK; } else { return Failed; } } KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList) : KMCommand(nullptr, msgList) , mDestFolder(destFolder) { } KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg) : KMCommand(nullptr, msg) , mDestFolder(destFolder) { } KMCommand::Result KMCopyCommand::execute() { setDeletesItself(true); Akonadi::Item::List listItem = retrievedMsgs(); Akonadi::ItemCopyJob *job = new Akonadi::ItemCopyJob(listItem, Akonadi::Collection(mDestFolder.id()), this); connect(job, &KIO::Job::result, this, &KMCopyCommand::slotCopyResult); return OK; } void KMCopyCommand::slotCopyResult(KJob *job) { if (job->error()) { // handle errors showJobError(job); setResult(Failed); } qobject_cast(job); Q_EMIT completed(this); deleteLater(); } KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList) : KMCommand(nullptr, msgList) , mDestFolder(destFolder) { fetchScope().fetchAllAttributes(); fetchScope().fetchFullPayload(); } KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg) : KMCopyDecryptedCommand(destFolder, Akonadi::Item::List{ msg }) { } KMCommand::Result KMCopyDecryptedCommand::execute() { setDeletesItself(true); const auto items = retrievedMsgs(); for (const auto &item : items) { // Decrypt if (!item.hasPayload()) { continue; } const auto msg = item.payload(); bool wasEncrypted; auto decMsg = MailCommon::CryptoUtils::decryptMessage(msg, wasEncrypted); if (!wasEncrypted) { decMsg = msg; } Akonadi::Item decItem; decItem.setMimeType(KMime::Message::mimeType()); decItem.setPayload(decMsg); auto job = new Akonadi::ItemCreateJob(decItem, mDestFolder, this); connect(job, &Akonadi::Job::result, this, &KMCopyDecryptedCommand::slotAppendResult); mPendingJobs << job; } if (mPendingJobs.isEmpty()) { Q_EMIT completed(this); deleteLater(); } return KMCommand::OK; } void KMCopyDecryptedCommand::slotAppendResult(KJob *job) { mPendingJobs.removeOne(job); if (mPendingJobs.isEmpty()) { Q_EMIT completed(this); deleteLater(); } } KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref) : KMCommand(nullptr, msgList) , mDestFolder(destFolder) , mProgressItem(nullptr) , mRef(ref) { fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref) : KMCommand(nullptr, msg) , mDestFolder(destFolder) , mProgressItem(nullptr) , mRef(ref) { fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } void KMMoveCommand::slotMoveResult(KJob *job) { if (job->error()) { // handle errors showJobError(job); completeMove(Failed); } else { completeMove(OK); } } KMCommand::Result KMMoveCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif setEmitsCompletedItself(true); setDeletesItself(true); Akonadi::Item::List retrievedList = retrievedMsgs(); if (!retrievedList.isEmpty()) { if (mDestFolder.isValid()) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(retrievedList, mDestFolder, this); connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult); // group by source folder for undo std::sort(retrievedList.begin(), retrievedList.end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) { return lhs.storageCollectionId() < rhs.storageCollectionId(); }); Akonadi::Collection parent; int undoId = -1; for (const Akonadi::Item &item : qAsConst(retrievedList)) { if (item.storageCollectionId() <= 0) { continue; } if (parent.id() != item.storageCollectionId()) { parent = Akonadi::Collection(item.storageCollectionId()); undoId = kmkernel->undoStack()->newUndoAction(parent, mDestFolder); } kmkernel->undoStack()->addMsgToAction(undoId, item); } } else { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(retrievedList, this); connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult); } } else { deleteLater(); return Failed; } // TODO set SSL state according to source and destfolder connection? Q_ASSERT(!mProgressItem); mProgressItem = ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(), mDestFolder.isValid() ? i18n("Moving messages") : i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown); mProgressItem->setUsesBusyIndicator(true); connect(mProgressItem, &ProgressItem::progressItemCanceled, this, &KMMoveCommand::slotMoveCanceled); return OK; } void KMMoveCommand::completeMove(Result result) { if (mProgressItem) { mProgressItem->setComplete(); mProgressItem = nullptr; } setResult(result); Q_EMIT moveDone(this); Q_EMIT completed(this); deleteLater(); } void KMMoveCommand::slotMoveCanceled() { completeMove(Canceled); } KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref) : KMCommand() , mRef(ref) { // When trashing items from a virtual collection, they may each have a different // trash folder, so we need to handle it here carefully if (srcFolder.isVirtual()) { QHash cache; for (const auto &msg : msgList) { auto cacheIt = cache.find(msg.storageCollectionId()); if (cacheIt == cache.end()) { cacheIt = cache.insert(msg.storageCollectionId(), findTrashFolder(CommonKernel->collectionFromId(msg.storageCollectionId()))); } auto trashIt = mTrashFolders.find(*cacheIt); if (trashIt == mTrashFolders.end()) { trashIt = mTrashFolders.insert(*cacheIt, {}); } trashIt->push_back(msg); } } else { mTrashFolders.insert(findTrashFolder(srcFolder), msgList); } } KMTrashMsgCommand::TrashOperation KMTrashMsgCommand::operation() const { if (!mPendingMoves.isEmpty() && !mPendingDeletes.isEmpty()) { return Both; } else if (!mPendingMoves.isEmpty()) { return MoveToTrash; } else if (!mPendingDeletes.isEmpty()) { return Delete; } else { if (mTrashFolders.size() == 1) { if (mTrashFolders.begin().key().isValid()) { return MoveToTrash; } else { return Delete; } } else { return Unknown; } } } KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref) : KMTrashMsgCommand(findTrashFolder(srcFolder), Akonadi::Item::List{msg}, ref) { } Akonadi::Collection KMTrashMsgCommand::findTrashFolder(const Akonadi::Collection &folder) { Akonadi::Collection col = CommonKernel->trashCollectionFromResource(folder); if (!col.isValid()) { col = CommonKernel->trashCollectionFolder(); } if (folder != col) { return col; } return Akonadi::Collection(); } KMCommand::Result KMTrashMsgCommand::execute() { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif setEmitsCompletedItself(true); setDeletesItself(true); for (auto trashIt = mTrashFolders.begin(), end = mTrashFolders.end(); trashIt != end; ++trashIt) { const auto trash = trashIt.key(); if (trash.isValid()) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(*trashIt, trash, this); connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotMoveResult); mPendingMoves.push_back(job); // group by source folder for undo std::sort(trashIt->begin(), trashIt->end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) { return lhs.storageCollectionId() < rhs.storageCollectionId(); }); Akonadi::Collection parent; int undoId = -1; for (const Akonadi::Item &item : qAsConst(*trashIt)) { if (item.storageCollectionId() <= 0) { continue; } if (parent.id() != item.storageCollectionId()) { parent = Akonadi::Collection(item.storageCollectionId()); undoId = kmkernel->undoStack()->newUndoAction(parent, trash); } kmkernel->undoStack()->addMsgToAction(undoId, item); } } else { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(*trashIt, this); connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotDeleteResult); mPendingDeletes.push_back(job); } } if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) { deleteLater(); return Failed; } // TODO set SSL state according to source and destfolder connection? if (!mPendingMoves.isEmpty()) { Q_ASSERT(!mMoveProgress); mMoveProgress = ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(), i18n("Moving messages"), QString(), true, KPIM::ProgressItem::Unknown); mMoveProgress->setUsesBusyIndicator(true); connect(mMoveProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled); } if (!mPendingDeletes.isEmpty()) { Q_ASSERT(!mDeleteProgress); mDeleteProgress = ProgressManager::createProgressItem(QLatin1String("delete") + ProgressManager::getUniqueID(), i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown); mDeleteProgress->setUsesBusyIndicator(true); connect(mMoveProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled); } return OK; } void KMTrashMsgCommand::slotMoveResult(KJob *job) { mPendingMoves.removeOne(job); if (job->error()) { // handle errors showJobError(job); completeMove(Failed); } else if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) { completeMove(OK); } } void KMTrashMsgCommand::slotDeleteResult(KJob *job) { mPendingDeletes.removeOne(job); if (job->error()) { showJobError(job); completeMove(Failed); } else if (mPendingDeletes.isEmpty() && mPendingMoves.isEmpty()) { completeMove(OK); } } void KMTrashMsgCommand::slotMoveCanceled() { completeMove(Canceled); } void KMTrashMsgCommand::completeMove(KMCommand::Result result) { if (result == Failed) { - for (auto job : mPendingMoves) { + for (auto job : qAsConst(mPendingMoves)) { job->kill(); } - for (auto job : mPendingDeletes) { + for (auto job : qAsConst(mPendingDeletes)) { job->kill(); } } if (mDeleteProgress) { mDeleteProgress->setComplete(); mDeleteProgress = nullptr; } if (mMoveProgress) { mMoveProgress->setComplete(); mMoveProgress = nullptr; } setResult(result); Q_EMIT moveDone(this); Q_EMIT completed(this); deleteLater(); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer) : KMCommand(parent, msg) , mViewer(viewer) { fetchScope().fetchFullPayload(true); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs) : KMCommand(parent, msgs) , mViewer(nullptr) { fetchScope().fetchFullPayload(true); } KMCommand::Result KMSaveAttachmentsCommand::execute() { KMime::Content::List contentsToSave; const Akonadi::Item::List lstItems = retrievedMsgs(); for (const Akonadi::Item &item : lstItems) { if (item.hasPayload()) { contentsToSave += item.payload()->attachments(); } else { qCWarning(KMAIL_LOG) << "Retrieved item has no payload? Ignoring for saving the attachments"; } } QList urlList; if (MessageViewer::Util::saveAttachments(contentsToSave, parentWidget(), urlList)) { if (mViewer) { mViewer->showOpenAttachmentFolderWidget(urlList); } return OK; } return Failed; } KMResendMessageCommand::KMResendMessageCommand(QWidget *parent, const Akonadi::Item &msg) : KMCommand(parent, msg) { fetchScope().fetchFullPayload(true); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); } KMCommand::Result KMResendMessageCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageComposer::Util::message(item); if (!msg) { return Failed; } MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id())); factory.setIdentityManager(KMKernel::self()->identityManager()); factory.setFolderIdentity(MailCommon::Util::folderIdentity(item)); KMime::Message::Ptr newMsg = factory.createResend(); newMsg->contentType()->setCharset(MimeTreeParser::NodeHelper::charset(msg.data())); KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); win->setMessage(newMsg, lastSign, lastEncrypt, false, true); win->show(); return OK; } KMShareImageCommand::KMShareImageCommand(const QUrl &url, QWidget *parent) : KMCommand(parent) , mUrl(url) { } KMCommand::Result KMShareImageCommand::execute() { KMime::Message::Ptr msg(new KMime::Message); uint id = 0; MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id); msg->contentType()->setCharset("utf-8"); KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id); win->setFocusToSubject(); win->addAttachment(mUrl, i18n("Image")); win->show(); return OK; } KMFetchMessageCommand::KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item, MessageViewer::Viewer *viewer, KMReaderMainWin *win) : KMCommand(parent, item) , mViewer(viewer) , mReaderMainWin(win) { // Workaround KMCommand::transferSelectedMsgs() expecting non-empty fetchscope fetchScope().fetchFullPayload(true); } Akonadi::ItemFetchJob *KMFetchMessageCommand::createFetchJob(const Akonadi::Item::List &items) { Q_ASSERT(items.size() == 1); Akonadi::ItemFetchJob *fetch = mViewer->createFetchJob(items.first()); fetchScope() = fetch->fetchScope(); return fetch; } KMCommand::Result KMFetchMessageCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.hasPayload()) { return Failed; } mItem = item; return OK; } KMReaderMainWin *KMFetchMessageCommand::readerMainWin() const { return mReaderMainWin; } Akonadi::Item KMFetchMessageCommand::item() const { return mItem; } diff --git a/src/searchdialog/kmsearchmessagemodel.cpp b/src/searchdialog/kmsearchmessagemodel.cpp index a621168b0..103ca2317 100644 --- a/src/searchdialog/kmsearchmessagemodel.cpp +++ b/src/searchdialog/kmsearchmessagemodel.cpp @@ -1,241 +1,241 @@ /* * Copyright (c) 2011-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; version 2 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, 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 "kmsearchmessagemodel.h" #include "MailCommon/MailUtil" #include "messagelist/messagelistutil.h" #include #include #include #include #include #include #include #include #include #include "kmail_debug.h" #include #include KMSearchMessageModel::KMSearchMessageModel(QObject *parent) : Akonadi::MessageModel(parent) { fetchScope().fetchFullPayload(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::All); } KMSearchMessageModel::~KMSearchMessageModel() { } static QString toolTip(const Akonadi::Item &item) { KMime::Message::Ptr msg = item.payload(); QColor bckColor = QApplication::palette().color(QPalette::ToolTipBase); QColor txtColor = QApplication::palette().color(QPalette::ToolTipText); const QString bckColorName = bckColor.name(); const QString txtColorName = txtColor.name(); const bool textIsLeftToRight = (QApplication::layoutDirection() == Qt::LeftToRight); const QString textDirection = textIsLeftToRight ? QStringLiteral("left") : QStringLiteral("right"); QString tip = QStringLiteral( "" ); tip += QStringLiteral( "" \ "" \ "" - ).arg(txtColorName).arg(bckColorName).arg(msg->subject()->asUnicodeString().toHtmlEscaped()).arg(textDirection); + ).arg(txtColorName, bckColorName, msg->subject()->asUnicodeString().toHtmlEscaped(), textDirection); tip += QStringLiteral( "" \ "
" \ "
" \ "%3" \ "
" \ "
" \ "" ); const QString htmlCodeForStandardRow = QStringLiteral( "" \ "" \ "" \ ""); QString content = MessageList::Util::contentSummary(item); if (textIsLeftToRight) { tip += htmlCodeForStandardRow.arg(i18n("From"), msg->from()->displayString()); tip += htmlCodeForStandardRow.arg(i18nc("Receiver of the email", "To"), msg->to()->displayString()); tip += htmlCodeForStandardRow.arg(i18n("Date"), QLocale().toString(msg->date()->dateTime())); if (!content.isEmpty()) { - tip += htmlCodeForStandardRow.arg(i18n("Preview")).arg(content.replace(QLatin1Char('\n'), QStringLiteral("
"))); + tip += htmlCodeForStandardRow.arg(i18n("Preview"), content.replace(QLatin1Char('\n'), QStringLiteral("
"))); } } else { tip += htmlCodeForStandardRow.arg(msg->from()->displayString()).arg(i18n("From")); tip += htmlCodeForStandardRow.arg(msg->to()->displayString()).arg(i18nc("Receiver of the email", "To")); tip += htmlCodeForStandardRow.arg(QLocale().toString(msg->date()->dateTime())).arg(i18n("Date")); if (!content.isEmpty()) { - tip += htmlCodeForStandardRow.arg(content.replace(QLatin1Char('\n'), QStringLiteral("
"))).arg(i18n("Preview")); + tip += htmlCodeForStandardRow.arg(content.replace(QLatin1Char('\n'), QStringLiteral("
")), i18n("Preview")); } } tip += QLatin1String( "" \ "" ); return tip; } int KMSearchMessageModel::columnCount(const QModelIndex &parent) const { if (collection().isValid() && !collection().contentMimeTypes().contains(QLatin1String("message/rfc822")) && collection().contentMimeTypes() != QStringList(QStringLiteral("inode/directory"))) { return 1; } if (!parent.isValid()) { return 6; // keep in sync with the column type enum } return 0; } QString KMSearchMessageModel::fullCollectionPath(Akonadi::Collection::Id id) const { QString path = m_collectionFullPathCache.value(id); if (path.isEmpty()) { path = MailCommon::Util::fullCollectionPath(Akonadi::Collection(id)); m_collectionFullPathCache.insert(id, path); } return path; } QVariant KMSearchMessageModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= rowCount()) { return QVariant(); } if (!collection().contentMimeTypes().contains(QLatin1String("message/rfc822"))) { if (role == Qt::DisplayRole) { return i18nc("@label", "This model can only handle email folders. The current collection holds mimetypes: %1", collection().contentMimeTypes().join(QLatin1Char(','))); } else { return QVariant(); } } Akonadi::Item item = itemForIndex(index); // Handle the most common case first, before calling payload(). if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == Collection) { if (item.storageCollectionId() >= 0) { return fullCollectionPath(item.storageCollectionId()); } return fullCollectionPath(item.parentCollection().id()); } if (!item.hasPayload()) { return QVariant(); } KMime::Message::Ptr msg = item.payload(); if (role == Qt::DisplayRole) { switch (index.column()) { case Subject: return msg->subject()->asUnicodeString(); case Sender: return msg->from()->asUnicodeString(); case Receiver: return msg->to()->asUnicodeString(); case Date: return QLocale().toString(msg->date()->dateTime()); case Size: if (item.size() == 0) { return i18nc("@label No size available", "-"); } else { return KFormat().formatByteSize(item.size()); } default: return QVariant(); } } else if (role == Qt::EditRole) { // used for sorting switch (index.column()) { case Subject: return msg->subject()->asUnicodeString(); case Sender: return msg->from()->asUnicodeString(); case Receiver: return msg->to()->asUnicodeString(); case Date: return msg->date()->dateTime(); case Size: return item.size(); default: return QVariant(); } } else if (role == Qt::ToolTipRole) { return toolTip(item); } return ItemModel::data(index, role); } QVariant KMSearchMessageModel::headerData(int section, Qt::Orientation orientation, int role) const { if (collection().isValid() && !collection().contentMimeTypes().contains(QLatin1String("message/rfc822")) && collection().contentMimeTypes() != QStringList(QStringLiteral("inode/directory"))) { return QVariant(); } if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case Collection: return i18nc("@title:column, folder (e.g. email)", "Folder"); default: return Akonadi::MessageModel::headerData((section - 1), orientation, role); } } return Akonadi::MessageModel::headerData((section - 1), orientation, role); }
" \ "
" \ "%1:" \ "
" \ "
" \ "%2" \ "