summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/keymap.cpp6
-rw-r--r--src/mainwindow.cpp52
-rw-r--r--src/mainwindow.h11
3 files changed, 69 insertions, 0 deletions
diff --git a/src/keymap.cpp b/src/keymap.cpp
index 7a08a58..76c6b60 100644
--- a/src/keymap.cpp
+++ b/src/keymap.cpp
@@ -32,6 +32,7 @@ QStringList KeyMap::knownActions()
QStringLiteral("archive"),
QStringLiteral("delete"),
QStringLiteral("restore"),
+ QStringLiteral("cleanup_stranded"),
QStringLiteral("spam"),
QStringLiteral("toggle_unread"),
QStringLiteral("mark_all_read"),
@@ -118,6 +119,11 @@ QList<QPair<QString, QString>> KeyMap::defaultBindings()
// Restore is only enabled in the trash view, so its key is dead
// elsewhere rather than doing something surprising.
{ QStringLiteral("Ctrl+R"), QStringLiteral("restore") },
+ // Item 103's cleanup. A chord rather than a plain key: it replaces the
+ // whole view, and it is reached from a menu far more often than from
+ // the keyboard. Ctrl+Shift+D is message_details and Ctrl+Alt+D is
+ // delete_thread, so this takes the T of "trash".
+ { QStringLiteral("Ctrl+Alt+T"), QStringLiteral("cleanup_stranded") },
{ QStringLiteral("Ctrl+Shift+S"), QStringLiteral("spam") },
{ QStringLiteral("Ctrl+U"), QStringLiteral("toggle_unread") },
// Shifted against Ctrl+U, which toggles unread on the selection: this
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 055e783..a1c01c3 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -865,6 +865,12 @@ void MainWindow::registerActions()
tr("Move the selected messages out of the trash"), [this]() {
restoreSelectedFromTrash();
});
+ addAction(QStringLiteral("cleanup_stranded"),
+ tr("Find &stranded deleted mail"),
+ tr("Show mail tagged deleted that is not in a trash folder"),
+ [this]() {
+ showStrandedDeletedMail();
+ });
addAction(QStringLiteral("spam"), tr("Mark &spam"),
tr("Add spam and remove inbox"), [this]() {
tagSelected({ QStringLiteral("spam") }, { QStringLiteral("inbox") },
@@ -1168,11 +1174,23 @@ void MainWindow::buildMenus()
// Separated from the entries above: those act on the selection, this edits
// a rule store shared with mailctl and changes nothing that is on screen.
messageMenu->addSeparator();
+ // A MENU entry and nothing else, at the user's request: "the cleanup
+ // should be a menu entry only, not to be confused with the filter Trash".
+ // It replaces the whole view like a filter does, so a sixth button beside
+ // the five filters would read as one of them.
+ messageMenu->addAction(m_actions.value(QStringLiteral("cleanup_stranded")));
messageMenu->addAction(m_actions.value(QStringLiteral("tag_rules")));
auto *viewMenu = menuBar()->addMenu(tr("&View"));
viewMenu->addAction(m_actions.value(QStringLiteral("prev_thread")));
viewMenu->addAction(m_actions.value(QStringLiteral("next_thread")));
+ viewMenu->addAction(m_actions.value(QStringLiteral("open_thread")));
+ viewMenu->addSeparator();
+ // The two clears. Both shipped keyboard-only, which is what
+ // everyActionIsReachableFromAMenu() exists to stop: an action reachable
+ // only by a chord is an action nobody discovers.
+ viewMenu->addAction(m_actions.value(QStringLiteral("clear_pane")));
+ viewMenu->addAction(m_actions.value(QStringLiteral("clear_selection")));
viewMenu->addSeparator();
viewMenu->addAction(m_actions.value(QStringLiteral("toggle_html")));
viewMenu->addAction(m_actions.value(QStringLiteral("load_remote")));
@@ -1217,6 +1235,10 @@ void MainWindow::buildMenus()
// The inverse of delete, and the theme's own name for it: the icon
// every desktop uses for taking something back out of the wastebasket.
{ QStringLiteral("restore"), QStringLiteral("edit-undelete") },
+ // A SEARCH, not a delete. The action reports what it finds and moves
+ // nothing, so an icon from the delete family would promise the one
+ // thing it deliberately does not do.
+ { QStringLiteral("cleanup_stranded"), QStringLiteral("system-search") },
{ QStringLiteral("undo"), QStringLiteral("edit-undo") },
{ QStringLiteral("spam"), QStringLiteral("mail-mark-junk") },
{ QStringLiteral("flag"), QStringLiteral("mail-mark-important") },
@@ -4649,6 +4671,36 @@ void MainWindow::restoreSelectedFromTrash()
Q_ARG(QString, QStringLiteral("restore_messages")));
}
+void MainWindow::showStrandedDeletedMail()
+{
+ // Not scoped to the selected account, deliberately. The stranded mail is
+ // an artefact of an old version rather than a view of anything, and the
+ // user wants to see all of it at once; the account dropdown is still there
+ // to narrow it by hand afterwards.
+ const QString trash = m_config.allTrashQuery();
+
+ // No account configures a trash folder: everything tagged `deleted` is by
+ // definition stranded, since there is nowhere for it to have gone. An
+ // empty exclusion must never be written as `not ()`, which notmuch parses
+ // without complaint and matches nothing, reporting a clean database.
+ const QString query =
+ trash.isEmpty()
+ ? QStringLiteral("tag:deleted")
+ : QStringLiteral("tag:deleted and not (%1)").arg(trash);
+
+ // Into the bar, like a filter: what ran is visible and editable, and
+ // AlreadyScoped stops runQuery() wrapping it in the selected account's
+ // path, which would hide every other account's stranded mail.
+ m_queryEdit->setText(query);
+ runQuery(FlatResult::No, AccountScope::AlreadyScoped);
+
+ // After runQuery(), which sets "Searching...": set before it, this would
+ // be overwritten and the user would be told nothing about what they are
+ // looking at.
+ m_statusLabel->setText(tr("Mail tagged deleted but not in a trash folder. "
+ "Select what should go and press Delete."));
+}
+
void MainWindow::restoreSelected(bool fallbackToInbox)
{
const QModelIndexList rows =
diff --git a/src/mainwindow.h b/src/mainwindow.h
index f9a075d..5b1621f 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -827,6 +827,17 @@ private:
/// folder it came from, one run in three.
void restoreSelectedFromTrash();
+ /// Runs the query that finds mail tagged `deleted` whose file never left
+ /// its original folder, which is what every version before item 103 left
+ /// behind. It REPORTS and moves nothing: acting on its own would be a bulk
+ /// delete with no selection behind it, and the user asked for something
+ /// they could come back to and review.
+ ///
+ /// Repeatable rather than a one-time startup migration, for the same
+ /// reason: mail reaches this state again whenever another client tags
+ /// without moving.
+ void showStrandedDeletedMail();
+
/// Moves each resolved message home, using the tags and paths the WORKER
/// reported rather than anything the model holds.
void restoreResolvedMessages(const QStringList &messageIds,