Commit 42755086 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Add folder-level 'Apply filter' actions to KMail

This adds 'Apply Filters on Folder' and 'Apply Filters on Folder and
its Subfolders' actions to KMail main menu. This allows to simply
execute any filter (or all filters) on an entire folder with a single
click.

This change also extends the Mail Filter Agent DBus API to allow callers
to pass Collection IDs to the Agent and the Agent will resolve the Items
itself, instead of the caller having to first obtain the Items and then
pass all of them via DBus.
parent d1a3ad6f
......@@ -63,7 +63,7 @@ option(KDEPIM_ENTERPRISE_BUILD "Enable features specific to the enterprise branc
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED DBus Network Test Widgets WebEngine WebEngineWidgets Xml)
set(LIBGRAVATAR_VERSION_LIB "5.6.40")
set(MAILCOMMON_LIB_VERSION_LIB "5.6.41")
set(MAILCOMMON_LIB_VERSION_LIB "5.6.42")
set(KDEPIM_APPS_LIB_VERSION_LIB "5.6.40")
set(MESSAGELIB_LIB_VERSION_LIB "5.6.41")
set(LIBKLEO_LIB_VERSION_LIB "5.6.40")
......
......@@ -313,6 +313,18 @@ void MailFilterAgent::filterItems(const QList< qint64 > &itemIds, int filterSet)
m_filterManager->applyFilters(items, static_cast<FilterManager::FilterSet>(filterSet));
}
void MailFilterAgent::filterCollections(const QList<qint64> &collections, int filterSet)
{
for (qint64 id: collections) {
auto ifj = new Akonadi::ItemFetchJob{ Akonadi::Collection{ id }, this };
ifj->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches);
connect(ifj, &Akonadi::ItemFetchJob::itemsReceived,
this, [=](const Akonadi::Item::List &items) {
m_filterManager->applyFilters(items, static_cast<FilterManager::FilterSet>(filterSet));
});
}
}
void MailFilterAgent::applySpecificFilters(const QList< qint64 > &itemIds, int requires, const QStringList &listFilters)
{
Akonadi::Item::List items;
......@@ -324,6 +336,21 @@ void MailFilterAgent::applySpecificFilters(const QList< qint64 > &itemIds, int r
m_filterManager->applySpecificFilters(items, static_cast<MailCommon::SearchRule::RequiredPart>(requires), listFilters);
}
void MailFilterAgent::applySpecificFiltersOnCollections(const QList<qint64> &colIds, const QStringList &listFilters)
{
// TODO: Actually calculate this based on the listFilters' requirements
const auto requires = MailCommon::SearchRule::CompleteMessage;
for (qint64 id : colIds) {
auto ifj = new Akonadi::ItemFetchJob{ Akonadi::Collection{ id }, this };
ifj->setDeliveryOption(Akonadi::ItemFetchJob::EmitItemsInBatches);
connect(ifj, &Akonadi::ItemFetchJob::itemsReceived,
this, [=](const Akonadi::Item::List &items) {
m_filterManager->applySpecificFilters(items, requires, listFilters);
});
}
}
void MailFilterAgent::filterItem(qint64 item, int filterSet, const QString &resourceId)
{
m_filterManager->filter(Akonadi::Item(item), static_cast<FilterManager::FilterSet>(filterSet), resourceId);
......
......@@ -50,7 +50,9 @@ public:
void filterItem(qint64 item, int filterSet, const QString &resourceId);
void filter(qint64 item, const QString &filterIdentifier, const QString &resourceId);
void filterCollections(const QList<qint64> &collections, int filterSet);
void applySpecificFilters(const QList< qint64 > &itemIds, int requires, const QStringList &listFilters);
void applySpecificFiltersOnCollections(const QList<qint64> &colIds, const QStringList &listFilters);
void reload();
......
......@@ -26,6 +26,16 @@
<arg name="filterSet" type="i" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="const QList&lt;qint64&gt; &amp;"/>
</method>
<method name="filterCollections">
<arg name="collections" type="ax" direction="in"/>
<arg name="filterSet" type="i" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="const QList&lt;qint64&gt; &amp;"/>
</method>
<method name="applySpecificFiltersOnCollections">
<arg name="collections" type="ax" direction="in"/>
<arg name="listFilters" type="as" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="const QList&lt;qint64&gt; &amp;"/>
</method>
<method name="reload"/>
<method name="showFilterLogDialog">
<arg direction="in" type="x" name="windowId" />
......
......@@ -2,7 +2,7 @@
the same menu entries at the same place in KMail and Kontact -->
<!DOCTYPE gui>
<gui version="523" name="kmmainwin" translationDomain="kmail">
<gui version="524" name="kmmainwin" translationDomain="kmail">
<MenuBar>
<Menu noMerge="1" name="file" >
<text>&amp;File</text>
......@@ -124,7 +124,16 @@
<Action name="akonadi_remove_duplicates" />
<Action name="remove_duplicate_recursive" />
<Separator/>
<Action name="apply_filters_on_folder" />
<Menu name="apply_filters_folder_actions">
<text>Apply Filters on Folder</text>
<Action name="apply_filters_folder" />
<ActionList name="menu_filter_folder_actions" />
</Menu>
<Menu name="apply_filters_folder_recursive_actions">
<text>Apply Filters on Folder and all its Subfolders</text>
<Action name="apply_filters_folder_recursive" />
<ActionList name="menu_filter_folder_recursive_actions" />
</Menu>
<Separator/>
<Action name="display_format_message" />
<Action name="prefer_html_external_refs" />
......
......@@ -1405,16 +1405,18 @@ void KMKernel::slotRunBackgroundTasks() // called regularly by timer
static Akonadi::Collection::List collect_collections(const QAbstractItemModel *model, const QModelIndex &parent)
{
Akonadi::Collection::List collections;
const int numberOfCollection(model->rowCount(parent));
for (int i = 0; i < numberOfCollection; ++i) {
const QModelIndex child = model->index(i, 0, parent);
Akonadi::Collection collection
= model->data(child, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
if (collection.isValid()) {
collections << collection;
QStack<QModelIndex> stack;
stack.push(parent);
while (!stack.isEmpty()) {
const auto idx = stack.pop();
if (idx.isValid()) {
collections << model->data(idx, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
}
for (int i = model->rowCount(idx) - 1; i >= 0; --i) {
stack.push(idx.child(i, 0));
}
collections += collect_collections(model, child);
}
return collections;
}
......@@ -1423,6 +1425,17 @@ Akonadi::Collection::List KMKernel::allFolders() const
return collect_collections(collectionModel(), QModelIndex());
}
Akonadi::Collection::List KMKernel::subfolders(const Akonadi::Collection &col) const
{
const auto idx = collectionModel()->match({}, Akonadi::EntityTreeModel::CollectionRole,
QVariant::fromValue(col), 1, Qt::MatchExactly);
if (!idx.isEmpty()) {
return collect_collections(collectionModel(), idx[0]);
}
return {};
}
void KMKernel::expireAllFoldersNow() // called by the GUI
{
mFolderCollectionMonitor->expireAllFolders(true /*immediate*/, entityTreeModel());
......
......@@ -383,6 +383,12 @@ public:
* is empty at startup.
*/
Akonadi::Collection::List allFolders() const;
/**
* Includes all subfolders of @p col, including the @p col itself.
*/
Akonadi::Collection::List subfolders(const Akonadi::Collection &col) const;
//
void selectCollectionFromId(Akonadi::Collection::Id id);
......
This diff is collapsed.
......@@ -86,6 +86,7 @@ class VacationManager;
namespace MailCommon {
class FolderSelectionDialog;
class FavoriteCollectionWidget;
class MailFilter;
}
class KMAIL_EXPORT KMMainWidget : public QWidget
......@@ -359,7 +360,8 @@ protected Q_SLOTS:
void slotCheckVacation();
void slotDebugSieve();
void slotApplyFilters();
void slotApplyFiltersOnFolder();
void slotApplyFiltersOnFolder(bool recursive);
void slotApplyFilterOnFolder(bool recursive);
void slotExpandThread();
void slotExpandAllThreads();
void slotCollapseThread();
......@@ -490,6 +492,8 @@ private:
void setCurrentThreadStatus(const Akonadi::MessageStatus &status, bool toggle);
void applyFilters(const Akonadi::Item::List &selectedMessages);
void applyFilters(const Akonadi::Collection::List &selectedCols);
void applyFilter(const Akonadi::Collection::List &selectedCols, const QString &filter);
/**
* Internal helper that creates the folder selection dialog used for the
......@@ -540,7 +544,6 @@ private Q_SLOTS:
void itemsFetchDone(KJob *job);
void slotServerSideSubscription();
void slotFetchItemsForFolderDone(KJob *job);
void slotServerStateChanged(Akonadi::ServerManager::State state);
void slotArchiveMails();
void slotChangeDisplayMessageFormat(MessageViewer::Viewer::DisplayFormatMessage format);
......@@ -563,6 +566,8 @@ private:
void slotPageIsScrolledToBottom(bool isAtBottom);
void printCurrentMessage(bool preview);
void replyCurrentMessageCommand(MessageComposer::ReplyStrategy strategy);
QAction *filterToAction(MailCommon::MailFilter *filter);
// Message actions
QAction *mDeleteAction;
QAction *mTrashThreadAction;
......@@ -582,7 +587,10 @@ private:
// Filter actions
KActionMenu *mFilterMenu;
QAction *mExpireConfigAction;
QAction *mApplyFiltersOnFolder;
KActionMenu *mApplyFilterFolderActionsMenu;
KActionMenu *mApplyFilterFolderRecursiveActionsMenu;
QAction *mApplyAllFiltersFolderAction;
QAction *mApplyAllFiltersFolderRecursiveAction;
// Custom template actions menu
KActionMenu *mTemplateMenu;
......@@ -635,6 +643,8 @@ private:
QVBoxLayout *mTopLayout;
bool mDestructed;
QList<QAction *> mFilterMenuActions;
QList<QAction *> mFilterFolderMenuActions;
QList<QAction *> mFilterFolderMenuRecursiveActions;
QList<QAction *> mFilterTBarActions;
QList<KMMetaFilterActionCommand *> mFilterCommands;
......
......@@ -2,7 +2,7 @@
the same menu entries at the same place in KMail and Kontact -->
<!DOCTYPE gui>
<gui version="523" name="kmmainwin" translationDomain="kmail">
<gui version="524" name="kmmainwin" translationDomain="kmail">
<MenuBar>
<Menu noMerge="1" name="file" >
<text>&amp;File</text>
......@@ -124,7 +124,16 @@
<Action name="akonadi_remove_duplicates" />
<Action name="remove_duplicate_recursive" />
<Separator/>
<Action name="apply_filters_on_folder" />
<Menu name="apply_filters_folder_actions">
<text>Apply Filters on Folder</text>
<Action name="apply_filters_folder" />
<ActionList name="menu_filter_folder_actions" />
</Menu>
<Menu name="apply_filters_folder_recursive_actions">
<text>Apply Filters on Folder and all its Subfolders</text>
<Action name="apply_filters_folder_recursive" />
<ActionList name="menu_filter_folder_recursive_actions" />
</Menu>
<Separator/>
<Action name="display_format_message" />
<Action name="prefer_html_external_refs" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment