diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3fecde21a..065b2af56 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,426 +1,425 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kmail\") qt5_generate_dbus_interface(kmkernel.h org.kde.kmail.kmail.xml OPTIONS -a) qt5_generate_dbus_interface(editor/kmcomposerwin.h org.kde.kmail.mailcomposer.xml OPTIONS -a) add_custom_target(kmail_xml ALL DEPENDS ${kmail_BINARY_DIR}/src/org.kde.kmail.kmail.xml DEPENDS ${kmail_BINARY_DIR}/src/org.kde.kmail.mailcomposer.xml COMMENT "Helper target for XML stuff. The Kontact plugin, KOrganizer and others depend on it." ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${GPGME_INCLUDES} ${AKONADI_INCLUDE_DIR} ) add_subdirectory(about) add_subdirectory(pics) add_subdirectory(icons) add_subdirectory(kcm_kpimidentities) add_subdirectory(kconf_update) add_subdirectory(kontactplugin) ########### kmailprivate ############### set(kmailprivate_attributes_LIB_SRCS attributes/taskattribute.cpp ) set(kmailprivate_folderarchive_LIB_SRCS folderarchive/folderarchiveaccountinfo.cpp folderarchive/folderarchiveutil.cpp folderarchive/folderarchivecache.cpp folderarchive/folderarchiveagentcheckcollection.cpp folderarchive/folderarchivemanager.cpp folderarchive/folderarchiveagentjob.cpp ) set(kmailprivate_collectionpage_LIB_SRCS collectionpage/collectiontemplatespage.cpp collectionpage/collectionmaintenancepage.cpp collectionpage/collectionviewpage.cpp collectionpage/collectionquotapage.cpp collectionpage/collectionquotawidget.cpp collectionpage/collectionmailinglistpage.cpp collectionpage/collectionshortcutpage.cpp ) set(kmailprivate_configuredialog_LIB_SRCS configuredialog/configagentdelegate.cpp configuredialog/configuredialoglistview.cpp configuredialog/configuredialog.cpp configuredialog/configuredialog_p.cpp configuredialog/configuremiscpage.cpp configuredialog/configuresecuritypage.cpp configuredialog/configurecomposerpage.cpp configuredialog/configureappearancepage.cpp configuredialog/configureaccountpage.cpp configuredialog/configurepluginpage.cpp configuredialog/colorlistbox.cpp ) set(kmailprivate_configureplugins_LIB_SRCS configuredialog/configureplugins/configurepluginslistwidget.cpp ) set(kmailprivate_searchdialog_LIB_SRCS searchdialog/kmsearchmessagemodel.cpp - searchdialog/kmsearchfilterproxymodel.cpp searchdialog/searchpatternwarning.cpp searchdialog/kmailsearchpatternedit.cpp searchdialog/searchwindow.cpp searchdialog/searchdescriptionattribute.cpp searchdialog/incompleteindexdialog.cpp ) set(kmailprivate_identity_LIB_SRCS identity/identitylistview.cpp identity/identitydialog.cpp identity/xfaceconfigurator.cpp identity/identitypage.cpp identity/newidentitydialog.cpp identity/identityeditvcarddialog.cpp identity/identityaddvcarddialog.cpp identity/identityinvalidfolder.cpp identity/identityfolderrequester.cpp ) set(kmailprivate_editor_LIB_SRCS editor/widgets/snippetwidget.cpp editor/kmcomposereditorng.cpp editor/composer.cpp editor/codec/codecaction.cpp editor/codec/codecmanager.cpp editor/kmcomposerwin.cpp editor/attachment/attachmentcontroller.cpp editor/attachment/attachmentview.cpp editor/widgets/cryptostateindicatorwidget.cpp editor/validatesendmailshortcut.cpp editor/kmcomposerglobalaction.cpp editor/kmcomposerupdatetemplatejob.cpp editor/kmcomposercreatenewcomposerjob.cpp ) set(kmailprivate_warningwidgets_LIB_SRCS warningwidgets/externaleditorwarning.cpp warningwidgets/attachmentmissingwarning.cpp ) set(kmailprivate_editor_potentialphishingemail_SRCS editor/potentialphishingemail/potentialphishingemailwarning.cpp editor/potentialphishingemail/potentialphishingemailjob.cpp editor/potentialphishingemail/potentialphishingdetaildialog.cpp editor/potentialphishingemail/potentialphishingdetailwidget.cpp ) set(kmailprivate_dialogs_LIB_SRCS dialog/archivefolderdialog.cpp dialog/addemailtoexistingcontactdialog.cpp dialog/kmknotify.cpp ) set(kmailprivate_job_LIB_SRCS job/addressvalidationjob.cpp job/createnewcontactjob.cpp job/addemailtoexistingcontactjob.cpp job/createtaskjob.cpp job/savedraftjob.cpp job/removeduplicatemailjob.cpp job/createfollowupreminderonexistingmessagejob.cpp job/removecollectionjob.cpp job/saveasfilejob.cpp job/markallmessagesasreadinfolderandsubfolderjob.cpp job/removeduplicatemessageinfolderandsubfolderjob.cpp job/handleclickedurljob.cpp job/composenewmessagejob.cpp job/opencomposerjob.cpp job/newmessagejob.cpp job/opencomposerhiddenjob.cpp job/fillcomposerjob.cpp job/createreplymessagejob.cpp job/createforwardmessagejob.cpp ) set(kmailprivate_widgets_LIB_SRCS widgets/collectionpane.cpp widgets/vacationscriptindicatorwidget.cpp widgets/displaymessageformatactionmenu.cpp widgets/statusbarlabeltoggledstate.cpp widgets/kactionmenutransport.cpp widgets/kactionmenuaccount.cpp ) set(kmailprivate_tag_LIB_SRCS tag/tagactionmanager.cpp tag/tagselectdialog.cpp ) set(kmailprivate_plugininterface_LIB_SRCS plugininterface/kmailplugininterface.cpp ) set(kmailprivate_editor_plugininterface_LIB_SRCS editor/plugininterface/kmailplugineditormanagerinterface.cpp editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.cpp ) set(kmailprivate_checkindexing_LIB_SRCS search/checkindexingmanager.cpp search/checkindexingjob.cpp ) set(kmailprivate_sieveimapinstanceinterface_LIB_SRCS sieveimapinterface/kmailsieveimapinstanceinterface.cpp ) set(kmail_common_SRCS) ecm_qt_declare_logging_category(kmail_common_SRCS HEADER kmail_debug.h IDENTIFIER KMAIL_LOG CATEGORY_NAME org.kde.pim.kmail) set(kmailprivate_LIB_SRCS ${kmail_common_SRCS} kmmainwin.cpp settings/kmailsettings.cpp kmreaderwin.cpp kmsystemtray.cpp undostack.cpp kmkernel.cpp kmcommands.cpp kmreadermainwin.cpp kmstartup.cpp kmmainwidget.cpp aboutdata.cpp mailserviceimpl.cpp secondarywindow.cpp util.cpp messageactions.cpp foldershortcutactionmanager.cpp kmlaunchexternalcomponent.cpp manageshowcollectionproperties.cpp kmmigrateapplication.cpp ${kmailprivate_configureplugins_LIB_SRCS} ${kmailprivate_attributes_LIB_SRCS} ${kmailprivate_tag_LIB_SRCS} ${kmailprivate_job_LIB_SRCS} ${kmailprivate_widgets_LIB_SRCS} ${kmailprivate_dialogs_LIB_SRCS} ${kmailprivate_warningwidgets_LIB_SRCS} ${kmailprivate_folderarchive_LIB_SRCS} ${kmailprivate_collectionpage_LIB_SRCS} ${kmailprivate_configuredialog_LIB_SRCS} ${kmailprivate_searchdialog_LIB_SRCS} ${kmailprivate_identity_LIB_SRCS} ${kmailprivate_editor_LIB_SRCS} ${kmailprivate_editor_potentialphishingemail_SRCS} ${kmailprivate_plugininterface_LIB_SRCS} ${kmailprivate_editor_plugininterface_LIB_SRCS} ${kmailprivate_checkindexing_LIB_SRCS} ${kmailprivate_sieveimapinstanceinterface_LIB_SRCS} ) qt5_generate_dbus_interface(editor/kmcomposerwin.h org.kde.kmail.mailcomposer.xml OPTIONS -a) qt5_add_dbus_adaptor(kmailprivate_LIB_SRCS ${kmail_BINARY_DIR}/src/org.kde.kmail.mailcomposer.xml editor/kmcomposerwin.h KMComposerWin ) qt5_add_dbus_adaptor(kmailprivate_LIB_SRCS ${kmail_BINARY_DIR}/src/org.kde.kmail.kmail.xml kmkernel.h KMKernel ) qt5_add_dbus_adaptor(kmailprivate_LIB_SRCS ${MAILTRANSPORT_DBUS_SERVICE} mailserviceimpl.h KMail::MailServiceImpl ) qt5_add_dbus_interfaces(kmailprivate_LIB_SRCS ${kmail_BINARY_DIR}/src/org.kde.kmail.kmail.xml ) qt5_add_dbus_interfaces(kmailprivate_LIB_SRCS ../agents/archivemailagent/org.freedesktop.Akonadi.ArchiveMailAgent.xml ) qt5_add_dbus_interfaces(kmailprivate_LIB_SRCS ../agents/sendlateragent/org.freedesktop.Akonadi.SendLaterAgent.xml ) qt5_add_dbus_interfaces(kmailprivate_LIB_SRCS dbusinterface/org.freedesktop.Akonadi.NewMailNotifier.xml ) qt5_add_dbus_interfaces(kmailprivate_LIB_SRCS ../agents/followupreminderagent/org.freedesktop.Akonadi.FollowUpReminder.xml ) qt5_add_dbus_interfaces(kmailprivate_LIB_SRCS ../agents/mailfilteragent/org.freedesktop.Akonadi.MailFilterAgent.xml ) ki18n_wrap_ui(kmailprivate_LIB_SRCS ui/composercryptoconfiguration.ui ui/warningconfiguration.ui ui/smimeconfiguration.ui ui/miscpagemaintab.ui ui/securitypagegeneraltab.ui ui/securitypagemdntab.ui ui/identitypage.ui ui/accountspagereceivingtab.ui ui/searchwindow.ui ui/incompleteindexdialog.ui ) # KCFG files. The main kmail.kcfg is configured by CMake and put # in the build directory. if(KDEPIM_ENTERPRISE_BUILD) set(WARN_TOOMANY_RECIPIENTS_DEFAULT true) else() set(WARN_TOOMANY_RECIPIENTS_DEFAULT false) endif() configure_file(settings/kmail.kcfg.cmake ${CMAKE_CURRENT_BINARY_DIR}/kmail.kcfg) kconfig_add_kcfg_files(kmailprivate_LIB_SRCS settings/globalsettings_kmail.kcfgc editor/custommimeheader/custommimeheader.kcfgc ) add_library(kmailprivate ${kmailprivate_LIB_SRCS}) generate_export_header(kmailprivate BASE_NAME kmail) target_link_libraries(kmailprivate PRIVATE KF5::TextWidgets KF5::I18n KF5::Gravatar KF5::Mime KF5::AkonadiCore KF5::AkonadiMime KF5::MessageCore KF5::MessageList KF5::MessageComposer KF5::PimCommon KF5::MailCommon KF5::TemplateParser KF5::IdentityManagement KF5::WindowSystem KF5::Notifications KF5::GuiAddons KF5::Crash KF5::Bookmarks KF5::KIOFileWidgets KF5::NotifyConfig KF5::MailTransportAkonadi KF5::KCMUtils KF5::Libkleo KF5::KSieveUi KF5::LibkdepimAkonadi KF5::KIOCore KF5::Contacts KF5::PimTextEdit KF5::MessageViewer KF5::SendLater KF5::FollowupReminder KF5::IconThemes KF5::XmlGui KF5::Completion KF5::Ldap KF5::AkonadiSearchDebug KF5::AkonadiSearchPIM KF5::WebEngineViewer KF5::SyntaxHighlighting ) target_include_directories(kmailprivate PUBLIC $) target_include_directories(kmailprivate PUBLIC $) target_include_directories(kmailprivate PUBLIC $) set_target_properties(kmailprivate PROPERTIES VERSION ${KDEPIM_LIB_VERSION} SOVERSION ${KDEPIM_LIB_SOVERSION} ) ########### kcm_kmail ############### set(kcm_kmail_PART_SRCS kcm_kmail.cpp ) add_library(kcm_kmail MODULE ${kcm_kmail_PART_SRCS}) target_link_libraries(kcm_kmail kmailprivate KF5::KCMUtils KF5::Completion KF5::I18n KF5::TextWidgets) ########### kmailpart ############### set(kmailpart_PART_SRCS kmail_part.cpp ${kmail_common_SRCS}) qt5_generate_dbus_interface(kmail_part.h org.kde.kmail.kmailpart.xml OPTIONS -a) qt5_add_dbus_adaptor(kmailpart_PART_SRCS ${kmail_BINARY_DIR}/src/org.kde.kmail.kmailpart.xml kmail_part.h KMailPart ) qt5_add_dbus_interfaces(kmailpart_PART_SRCS ${kmail_BINARY_DIR}/src/org.kde.kmail.kmailpart.xml) add_library(kmailpart MODULE ${kmailpart_PART_SRCS}) target_link_libraries(kmailpart kmailprivate KF5::PimCommon KF5::LibkdepimAkonadi KF5::KCMUtils KF5::Parts KF5::IconThemes KF5::TemplateParser ) ########### KMail executable ############### set(kmail_SRCS main.cpp ${kmail_common_SRCS}) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kmail.png") ecm_add_app_icon(kmail_SRCS ICONS ${ICONS_SRCS}) add_executable(kmail ${kmail_SRCS}) target_link_libraries(kmail KF5::KontactInterface kmailprivate KF5::PimCommon KF5::I18n KF5::TemplateParser KF5::Libkdepim KF5::Crash ) if(BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) add_subdirectory(folderarchive/autotests/) add_subdirectory(editor/potentialphishingemail/autotests) add_subdirectory(sieveimapinterface/tests/) endif() ########### install files ############### install(TARGETS kmailprivate ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(TARGETS kmail ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(PROGRAMS data/org.kde.kmail.desktop data/kmail_view.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kmail.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR} ) install(FILES data/org.kde.kmail.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES data/kmail_config_misc.desktop data/kmail_config_appearance.desktop data/kmail_config_identity.desktop data/kmail_config_accounts.desktop data/kmail_config_composer.desktop data/kmail_config_security.desktop data/kmail_config_plugins.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install(FILES kmcomposerui.rc kmmainwin.rc kmreadermainwin.rc kmail_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kmail2 ) install( FILES data/kmail2.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) install(FILES data/dbusmail.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(TARGETS kmailpart kcm_kmail DESTINATION ${KDE_INSTALL_PLUGINDIR} ) install(FILES ${kmail_BINARY_DIR}/src/org.kde.kmail.kmailpart.xml ${kmail_BINARY_DIR}/src/org.kde.kmail.kmail.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES data/kmail_addattachmentservicemenu.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/ServiceMenus) diff --git a/src/searchdialog/kmsearchfilterproxymodel.cpp b/src/searchdialog/kmsearchfilterproxymodel.cpp deleted file mode 100644 index 782ddb559..000000000 --- a/src/searchdialog/kmsearchfilterproxymodel.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (C) 2011-2017 Montel Laurent - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; see the file COPYING. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "kmsearchfilterproxymodel.h" -#include "kmsearchmessagemodel.h" -#include -#include - -using namespace KMail; - -KMSearchFilterProxyModel::KMSearchFilterProxyModel(QObject *parent) - : QSortFilterProxyModel(parent) -{ - setDynamicSortFilter(true); - setFilterCaseSensitivity(Qt::CaseInsensitive); -} - -KMSearchFilterProxyModel::~KMSearchFilterProxyModel() -{ -} - -bool KMSearchFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - if (right.model() && left.model() && left.column() == KMSearchMessageModel::Date) { - if (sourceModel()) { - const QDateTime leftData = - sourceModel()->data( - left.sibling(left.row(), KMSearchMessageModel::DateNotTranslated)).toDateTime(); - - const QDateTime rightData = - sourceModel()->data( - right.sibling(right.row(), KMSearchMessageModel::DateNotTranslated)).toDateTime(); - return leftData < rightData; - } else { - return false; - } - } - - if (right.model() && left.model() && left.column() == KMSearchMessageModel::Size) { - if (sourceModel()) { - const qint64 leftData = - sourceModel()->data( - left.sibling(left.row(), KMSearchMessageModel::SizeNotLocalized)).toLongLong(); - const qint64 rightData = - sourceModel()->data( - right.sibling(right.row(), KMSearchMessageModel::SizeNotLocalized)).toLongLong(); - return leftData < rightData; - } else { - return false; - } - } - return QSortFilterProxyModel::lessThan(left, right); -} diff --git a/src/searchdialog/kmsearchfilterproxymodel.h b/src/searchdialog/kmsearchfilterproxymodel.h deleted file mode 100644 index 495d825be..000000000 --- a/src/searchdialog/kmsearchfilterproxymodel.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (C) 2011-2017 Montel Laurent - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; see the file COPYING. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef KMSEARCHFILTERPROXYMODEL_H -#define KMSEARCHFILTERPROXYMODEL_H - -#include - -class QModelIndex; - -namespace KMail -{ -class KMSearchFilterProxyModel : public QSortFilterProxyModel -{ -public: - explicit KMSearchFilterProxyModel(QObject *parent); - ~KMSearchFilterProxyModel(); -protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; -}; -} - -#endif /* KMSEARCHFILTERPROXYMODEL_H */ - diff --git a/src/searchdialog/kmsearchmessagemodel.cpp b/src/searchdialog/kmsearchmessagemodel.cpp index 0adf3f29b..3ecd79b4a 100644 --- a/src/searchdialog/kmsearchmessagemodel.cpp +++ b/src/searchdialog/kmsearchmessagemodel.cpp @@ -1,242 +1,244 @@ /* * Copyright (c) 2011-2017 Montel Laurent * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; 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 #include "kmail_debug.h" #include #include KMSearchMessageModel::KMSearchMessageModel(QObject *parent) : Akonadi::MessageModel(parent) { fetchScope().fetchFullPayload(); fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::All); } KMSearchMessageModel::~KMSearchMessageModel() { } -QString toolTip(const Akonadi::Item &item) +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); tip += QStringLiteral( "" \ "
" \ "
" \ "%3" \ "
" \ "
" \ "" ); const QString htmlCodeForStandardRow = QStringLiteral( "" \ "" \ "" \ ""); QString content = MessageList::Util::contentSummary(item); if (textIsLeftToRight) { tip += htmlCodeForStandardRow.arg(i18n("From")).arg(msg->from()->displayString()); tip += htmlCodeForStandardRow.arg(i18nc("Receiver of the email", "To")).arg(msg->to()->displayString()); tip += htmlCodeForStandardRow.arg(i18n("Date")).arg(QLocale().toString(msg->date()->dateTime())); if (!content.isEmpty()) { tip += htmlCodeForStandardRow.arg(i18n("Preview")).arg(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 += QLatin1String( "" \ "" ); return tip; } int KMSearchMessageModel::columnCount(const QModelIndex &parent) const { if (collection().isValid() && !collection().contentMimeTypes().contains(QStringLiteral("message/rfc822")) && collection().contentMimeTypes() != QStringList(QStringLiteral("inode/directory"))) { return 1; } if (!parent.isValid()) { - return 8; // keep in sync with the column type enum + 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(QStringLiteral("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 Collection: - if (item.storageCollectionId() >= 0) { - return MailCommon::Util::fullCollectionPath(Akonadi::Collection(item.storageCollectionId())); - } - return MailCommon::Util::fullCollectionPath(item.parentCollection()); 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()); } - case SizeNotLocalized: - return item.size(); - case DateNotTranslated: - return msg->date()->dateTime(); default: return QVariant(); } - } else if (role == Qt::EditRole) { + } else if (role == Qt::EditRole) { // used for sorting switch (index.column()) { - case Collection: - if (item.storageCollectionId() >= 0) { - return MailCommon::Util::fullCollectionPath(Akonadi::Collection(item.storageCollectionId())); - } - return MailCommon::Util::fullCollectionPath(item.parentCollection()); 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 SizeNotLocalized: case Size: return item.size(); - case DateNotTranslated: - return msg->date()->dateTime(); 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(QStringLiteral("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); } diff --git a/src/searchdialog/kmsearchmessagemodel.h b/src/searchdialog/kmsearchmessagemodel.h index 7918337fc..40a2f956a 100644 --- a/src/searchdialog/kmsearchmessagemodel.h +++ b/src/searchdialog/kmsearchmessagemodel.h @@ -1,58 +1,62 @@ /* * Copyright (c) 2011-2017 Montel Laurent * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; 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. */ #ifndef KMSEARCHMESSAGEMODEL_H #define KMSEARCHMESSAGEMODEL_H #include +#include class KMSearchMessageModel : public Akonadi::MessageModel { Q_OBJECT public: enum Column { Collection, Subject, Sender, Receiver, Date, - Size, - DateNotTranslated, - SizeNotLocalized + Size }; explicit KMSearchMessageModel(QObject *parent = nullptr); ~KMSearchMessageModel(); int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; +private: + QString fullCollectionPath(Akonadi::Collection::Id id) const; + + mutable QHash m_collectionFullPathCache; + }; #endif diff --git a/src/searchdialog/searchwindow.cpp b/src/searchdialog/searchwindow.cpp index 2a09f2b15..b65b2ceac 100644 --- a/src/searchdialog/searchwindow.cpp +++ b/src/searchdialog/searchwindow.cpp @@ -1,975 +1,978 @@ /* * kmail: KDE mail client * Copyright (c) 1996-1998 Stefan Taferner * Copyright (c) 2001 Aaron J. Seigo * Copyright (c) 2010 Till Adam * Copyright (C) 2011-2017 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 "incompleteindexdialog.h" #include "searchwindow.h" #include "helper_p.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 "kmsearchfilterproxymodel.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 #include using namespace KPIM; using namespace MailCommon; using namespace KMail; SearchWindow::SearchWindow(KMMainWidget *widget, const Akonadi::Collection &collection) : QDialog(nullptr), mCloseRequested(false), mSortColumn(0), mSortOrder(Qt::AscendingOrder), mSearchJob(nullptr), mResultModel(nullptr), mKMMainWidget(widget), mAkonadiStandardAction(nullptr) { 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); 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); 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, SIGNAL(doubleClicked(Akonadi::Item)), this, SLOT(slotViewMsg(Akonadi::Item))); connect(mUi.mLbxMatches, SIGNAL(currentChanged(Akonadi::Item)), this, SLOT(slotCurrentChanged(Akonadi::Item))); 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"), this, SLOT(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"), this, SLOT(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); - KMSearchFilterProxyModel *sortproxy = new KMSearchFilterProxyModel(mResultModel); + 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) { if (mCollectionId.isEmpty()) { mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to select collections.")); return; } } else { mCollectionId = mSelectMultiCollectionDialog->selectedCollection(); } searchCollections.reserve(mCollectionId.count()); for (const Akonadi::Collection &col : qAsConst(mCollectionId)) { searchCollections << col; } if (searchCollections.isEmpty()) { mUi.mSearchFolderEdt->setEnabled(true); mSearchPatternWidget->showWarningPattern(QStringList() << i18n("You forgot to select collections.")); mQuery = Akonadi::SearchQuery(); return; } } 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); QMetaObject::invokeMethod(this, "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())); if (mSearchJob) { mSearchJob = nullptr; } enableGUI(); mUi.mSearchFolderEdt->setEnabled(true); mUi.mStatusLbl->setText(i18n("Search failed.")); } else { if (Akonadi::SearchCreateJob *searchJob = qobject_cast(mSearchJob)) { mFolder = searchJob->createdCollection(); } else if (Akonadi::CollectionModifyJob *modifyJob = qobject_cast(mSearchJob)) { 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); mSearchJob = nullptr; 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; } QMenu *menu = new QMenu(this); updateContextMenuActions(); // 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); delete menu; } 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() { KMCommand *command = new KMPrintCommand(this, selectedMessage()); 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; }
" \ "
" \ "%1:" \ "
" \ "
" \ "%2" \ "