From 51f04ffc3c17da59b6072c4362029c45b780de82 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 15 Aug 2026 11:00:58 +0200 Subject: feat(filters): put the four built-in filters on the query row Item 93, the UI half. Unread, Inbox, Flagged and Sent are buttons the application ships, sitting first on the row, ahead of the user's pinned saved queries. runFilter() is runSavedQuery()'s opposite in the one way that matters: it READS the account box and never writes it. That 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. A saved query keeps setting the account from what it stored, because it is a destination and states its own scope. runQuery() gains an AccountScope parameter. A filter's text arrives already resolved in the selected account's scope, and scoping it again would put path:"work/Sent/**" inside path:"work/**". Two migration changes, both of which unpin rather than delete: - Sent is no longer migrated from the INI into queries.json. The built-in filter covers it, and migrating one too would put two Sent buttons on the row, one editable and one not. - A stored entry naming a known generator is unpinned on load, which is what every install upgraded through 0.19.0 carries. It keeps its name and its generator and moves to the menu. Deleting it would be data loss on a file whose readers are supposed to preserve what they do not own. The test suite needed the same distinction the design makes. savedQueryButtonLabels() now skips the filters, and savedQueryButton(window, label) replaces five positional row->findChild() lookups that were silently returning Unread. One rendering probe had to be fixed rather than adapted. replyRowsKeepTheirTextUnderTheThreadLine resized the window to 300px, and four more buttons pushed the reply row below the viewport: the pixel loop then ran zero times and reported "0 pixels, the row was painted over", which is a different defect from the one it exists to catch. It gets 600px and a guard asserting the row is really inside the viewport, so the next person to shrink it gets told the truth. Verified by putting 300 back: the guard names the row at 83..165 in an 82px viewport. --- src/mainwindow.h | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.h b/src/mainwindow.h index 80c7d2b..bb1ba75 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -274,12 +274,23 @@ public: enum class FlatResult { No, Yes }; Q_ENUM(FlatResult) + /// Whether runQuery() applies the selected account's scope to the bar text. + /// + /// Apply is right for anything the user typed or a saved query put there. + /// AlreadyScoped is for a built-in filter, whose text was resolved through + /// Config::resolvedQuery(query, accountKey) and already carries the scope: + /// scoping it a second time would wrap path:"work/Sent/**" in + /// path:"work/**", which is the double scope item 93 exists to avoid. + enum class AccountScope { Apply, AlreadyScoped }; + Q_ENUM(AccountScope) + private: /// The real query runner. Kept off the slot list deliberately: a slot with /// a defaulted argument does not satisfy QObject::connect, which matches /// signal and slot arity at compile time, so the zero-argument slot below /// is what widgets connect to. - void runQuery(FlatResult flat); + void runQuery(FlatResult flat, + AccountScope scope = AccountScope::Apply); /// Builds the row of saved-query buttons, the overflow menu and Sent. /// @@ -295,6 +306,19 @@ private: /// twice. Setting the dropdown also shows the user what scope they are in. void runSavedQuery(const SavedQuery &saved); + /// Runs a built-in filter in whatever account scope is currently selected. + /// + /// The opposite of runSavedQuery() in the one way that matters: it does NOT + /// touch the account box. A filter narrows what the user is already looking + /// at, so the dropdown is its input rather than something it overwrites, + /// which is item 90's defect and item 93's design. + /// + /// The query text is resolved here rather than left to runQuery()'s own + /// scoping, because a generator must be asked for the account's own query: + /// Sent wrapped in a scope double-scopes and works only by accident of + /// path: being hierarchical. See Config::resolvedQuery(query, accountKey). + void runFilter(const SavedQuery &filter); + /// Names the current query and stores it in queries.json. void saveCurrentQuery(); -- cgit v1.2.3