diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.cpp | 36 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 62 | ||||
| -rw-r--r-- | src/mainwindow.h | 26 |
3 files changed, 96 insertions, 28 deletions
diff --git a/src/config.cpp b/src/config.cpp index 0b65053..ece1fb9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -482,22 +482,16 @@ void Config::loadSavedQueries(const QString &configPath, QSettings &settings) } settings.endGroup(); - // Sent was a hardcoded button beside the saved queries and becomes an - // ordinary row here, so it can be reordered, renamed, unpinned or - // removed like any other. It stays GENERATED, so it still follows the - // accounts. Appended last, where the button already sat. + // Sent is NOT migrated into queries.json any more. It used to become an + // ordinary saved query here, so the hardcoded button could be + // reordered, renamed or removed; item 93 makes it one of four built-in + // filters instead, which are shipped rather than stored. Migrating it + // as well would put two Sent buttons on the row, one of them the user's + // to edit and one not. // - // Only when an account actually configures a sent folder: the button - // was hidden entirely otherwise, and migrating a row that always finds - // nothing would be worse than what it replaces. - if (!allSentQuery().isEmpty()) { - SavedQuery sent; - sent.name = QStringLiteral("Sent"); - sent.generated = QStringLiteral("sent"); - sent.pinned = true; - sent.flat = true; - m_savedQueries.append(sent); - } + // Nothing is lost: the built-in Sent resolves through the same + // generator, so it still follows the accounts, and it now composes with + // the account dropdown rather than resetting it. // Order is alphabetical here because childKeys() is genuinely all the // INI knows. The user reorders once and it sticks from then on. @@ -585,6 +579,18 @@ void Config::loadSavedQueries(const QString &configPath, QSettings &settings) continue; } + // A stored entry naming a generator now duplicates a BUILT-IN filter of + // the same name, since item 93 ships all four rather than storing them. + // 0.19.0 migrated the hardcoded Sent button into exactly such an entry, + // so every existing install has one. + // + // Unpinned, never dropped: the row would otherwise carry two Sent + // buttons, one the user's to edit and one not. Deleting it would be + // data loss on a file whose readers are supposed to preserve what they + // do not own, and an unpin is reversible from the UI. + if (query.isGenerated() && isKnownGenerator(query.generated)) + query.pinned = false; + for (auto it = object.begin(); it != object.end(); ++it) { static const QStringList known = { QStringLiteral("name"), QStringLiteral("query"), diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c6df95c..6bd91d9 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); @@ -1928,6 +1946,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 +2041,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 +2051,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()) 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(); |
