aboutsummaryrefslogtreecommitdiffstats
path: root/src/mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-15 11:13:28 +0200
committerDanilo M. <danix@danix.xyz>2026-08-15 11:13:28 +0200
commit4757cc5780de1f7a7478cf07948f5659913d31b5 (patch)
tree80a0366d74cb98b5499abeb9209a689d8b834292 /src/mainwindow.cpp
parente6e18849bd5aa49dbdd299982d39a92e7360d0aa (diff)
parent8ea004e67427693139ddd55384c7963d856650f0 (diff)
downloadqtmaildir-4757cc5780de1f7a7478cf07948f5659913d31b5.tar.gz
qtmaildir-4757cc5780de1f7a7478cf07948f5659913d31b5.zip
Merge branch 'builtin-filters': four built-in query filters
Items 93, 95 and 90. The query row starts with Unread, Inbox, Flagged and Sent, shipped by the application rather than pinned by the user, and they compose with the account dropdown instead of resetting it. Item 90 is absorbed: the button that cleared the account selection is no longer a saved query at all. Item 95 was found by hand testing this and is not caused by it: no entry in the saved query overflow menu had ever been runnable, because Qt emits no triggered for an action that owns a submenu.
Diffstat (limited to 'src/mainwindow.cpp')
-rw-r--r--src/mainwindow.cpp87
1 files changed, 71 insertions, 16 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index c6df95c..bf1a33b 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1717,12 +1717,31 @@ void MainWindow::buildSavedQueryRow(QWidget *parent, QVBoxLayout *layout)
auto *box = new QHBoxLayout(row);
box->setContentsMargins(0, 0, 0, 0);
- // Sent is an ordinary row here, not a hardcoded button beside the others.
- // It is still GENERATED, so its query is composed from the accounts' `sent`
- // keys at click time and correcting a folder name stays a config edit and
- // nothing else; what changed is that the entry can now be reordered,
- // renamed, unpinned or removed like every other, instead of being the one
- // control on the row the user did not own.
+ // The built-in filters come first, in their own fixed order, and they are
+ // not saved queries: they are shipped, they are not in queries.json, and
+ // the user cannot edit or delete them (item 93). They are what the row is
+ // FOR; the pinned saved queries below them are the transitional half that
+ // item 94 removes.
+ for (const SavedQuery &filter : Config::builtinFilters()) {
+ // Sent with no account configuring a sent folder finds nothing by
+ // construction. Hidden rather than present and empty, which is what the
+ // hardcoded Sent button did and is worth keeping: a control that always
+ // returns nothing reads as broken rather than as absent.
+ if (m_config.resolvedQuery(filter, QString())
+ == Config::matchNothingQuery())
+ continue;
+
+ auto *button = new QPushButton(filter.name, row);
+ // A stable object name per filter, so a test finds the button without
+ // depending on the label, which is translated.
+ button->setObjectName(filter.generated + QStringLiteral("Button"));
+ connect(button, &QPushButton::clicked, this,
+ [this, filter]() { runFilter(filter); });
+ box->addWidget(button);
+ }
+
+ // The user's own saved queries. A pinned one is still a button, beside the
+ // filters, until item 94 makes the menu their only home.
QList<SavedQuery> unpinned;
for (const SavedQuery &saved : m_config.savedQueries()) {
// A generator whose accounts configure nothing produces a button that
@@ -1736,10 +1755,9 @@ void MainWindow::buildSavedQueryRow(QWidget *parent, QVBoxLayout *layout)
continue;
}
auto *button = new QPushButton(saved.name, row);
- // The generated entries keep a stable object name so a test can find
- // the sent button without depending on what the user renamed it to.
- if (saved.generated == QStringLiteral("sent"))
- button->setObjectName(QStringLiteral("sentButton"));
+ // No object name here any more. "sentButton" now belongs to the BUILT-IN
+ // Sent filter, and a migrated Sent entry claiming it too would give two
+ // buttons one name, so findChild() would return whichever came first.
connect(button, &QPushButton::clicked, this,
[this, saved]() { runSavedQuery(saved); });
addSavedQueryActions(button, saved);
@@ -1761,12 +1779,29 @@ void MainWindow::buildSavedQueryRow(QWidget *parent, QVBoxLayout *layout)
auto *menu = new QMenu(menuButton);
for (const SavedQuery &saved : unpinned) {
QAction *action = menu->addAction(saved.name);
- connect(action, &QAction::triggered, this,
- [this, saved]() { runSavedQuery(saved); });
+
// A menu entry has no context menu of its own, so its own submenu
- // carries the same three actions; an unpinned query would
- // otherwise be the one thing that cannot be edited or deleted.
+ // carries the same actions; an unpinned query would otherwise be
+ // the one thing that cannot be edited or deleted.
auto *entryMenu = new QMenu(menu);
+
+ // Running the query is an item INSIDE that submenu, and must be:
+ // Qt does not emit triggered for an action that owns a menu, so a
+ // connection on `action` itself never fires and clicking the entry
+ // only opens the submenu. That shipped, and went unnoticed while
+ // the menu was the rarely-used half and the user's queries were
+ // pinned buttons. Item 93 moved every query into the menu, and item
+ // 94 makes it their only home.
+ auto *run = new QAction(tr("Run"), entryMenu);
+ run->setObjectName(QStringLiteral("runQuery"));
+ connect(run, &QAction::triggered, this,
+ [this, saved]() { runSavedQuery(saved); });
+ entryMenu->addAction(run);
+
+ auto *runSeparator = new QAction(entryMenu);
+ runSeparator->setSeparator(true);
+ entryMenu->addAction(runSeparator);
+
addSavedQueryActions(entryMenu, saved);
action->setMenu(entryMenu);
}
@@ -1928,6 +1963,22 @@ void MainWindow::runSavedQuery(const SavedQuery &saved)
runQuery(saved.flat ? FlatResult::Yes : FlatResult::No);
}
+void MainWindow::runFilter(const SavedQuery &filter)
+{
+ // The account box is READ and never written. That is the whole difference
+ // from runSavedQuery(), and it is item 90's defect: a filter narrows what
+ // the user is already looking at, so the dropdown is its input rather than
+ // something it resets on the way past.
+ const QString accountKey = m_accountBox->currentData().toString();
+
+ // Resolved here, in the account's scope, and put in the bar so what ran is
+ // visible and editable. runQuery() is told not to scope it again.
+ m_queryEdit->setText(m_config.resolvedQuery(filter, accountKey));
+
+ runQuery(filter.flat ? FlatResult::Yes : FlatResult::No,
+ AccountScope::AlreadyScoped);
+}
+
void MainWindow::saveCurrentQuery()
{
const QString query = m_queryEdit->text().trimmed();
@@ -2007,7 +2058,7 @@ void MainWindow::rebuildSavedQueryRow()
}
}
-void MainWindow::runQuery(FlatResult flat)
+void MainWindow::runQuery(FlatResult flat, AccountScope scope)
{
// Set on EVERY run, not only when Yes. This is the line that stops flat
// mode leaking: any query that is not the Sent button restores the tree,
@@ -2017,8 +2068,12 @@ void MainWindow::runQuery(FlatResult flat)
QString query = m_queryEdit->text().trimmed();
+ // A built-in filter arrives already resolved in the selected account's
+ // scope, because a generator has to be asked for the account's own query
+ // rather than have its all-accounts query wrapped. Scoping again here would
+ // put path:"work/Sent/**" inside path:"work/**".
const QString accountKey = m_accountBox->currentData().toString();
- if (!accountKey.isEmpty())
+ if (scope == AccountScope::Apply && !accountKey.isEmpty())
query = m_config.account(accountKey).scopedQuery(query);
if (query.isEmpty())