diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.cpp | 66 | ||||
| -rw-r--r-- | src/config.h | 21 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 50 |
3 files changed, 111 insertions, 26 deletions
diff --git a/src/config.cpp b/src/config.cpp index 8cd5e56..9edba50 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -51,6 +51,14 @@ constexpr int kMaxToolbarIconSize = 64; /// two-repo change and no hook stops tagging if it is half-deployed. constexpr int kQueriesFormatVersion = 1; +/// Generators a saved query may name in its `generated` field. +/// +/// A closed set, checked on load so a typo is reported rather than producing a +/// button that silently finds nothing. Adding one here needs no format bump: +/// an older build keeps the row and reports it, which is why an unknown +/// generator is a problem rather than a reason to drop the entry. +const QStringList kQueryGenerators = { QStringLiteral("sent") }; + } // namespace QString Account::scopedQuery(const QString &query) const @@ -457,9 +465,26 @@ 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. + // + // 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); + } + // Order is alphabetical here because childKeys() is genuinely all the // INI knows. The user reorders once and it sticks from then on. - if (!names.isEmpty() && !saveSavedQueries()) { + if (!m_savedQueries.isEmpty() && !saveSavedQueries()) { addProblem(QStringLiteral("Could not write saved queries to %1.") .arg(m_queriesPath)); } @@ -516,6 +541,26 @@ void Config::loadSavedQueries(const QString &configPath, QSettings &settings) query.query = object.value(QStringLiteral("query")).toString(); query.pinned = object.value(QStringLiteral("pinned")).toBool(false); query.account = object.value(QStringLiteral("account")).toString(); + query.generated = object.value(QStringLiteral("generated")).toString(); + // A generator carries its own view mode, so "sent" is flat whether or + // not the file says so. Storing it as a plain field would let a + // hand-edited or migrated-from-elsewhere row produce a THREADED sent + // view, which folds every reply back into the conversation the user + // sent one message into. The file may still set it for an ordinary + // query. + query.flat = object.value(QStringLiteral("flat")).toBool(false) + || query.generated == QStringLiteral("sent"); + + if (query.isGenerated() + && !kQueryGenerators.contains(query.generated)) { + // Reported but KEPT. A later build may know this generator, and + // dropping the row here would delete it from the file on the next + // save, which is the same data loss the unknown-field handling + // exists to prevent. + addProblem(QStringLiteral("Saved query '%1' uses an unknown " + "generator '%2' and will find nothing.") + .arg(query.name, query.generated)); + } if (query.name.isEmpty()) { addProblem(QStringLiteral("A saved query in %1 has no name and was " @@ -526,7 +571,8 @@ void Config::loadSavedQueries(const QString &configPath, QSettings &settings) for (auto it = object.begin(); it != object.end(); ++it) { static const QStringList known = { QStringLiteral("name"), QStringLiteral("query"), - QStringLiteral("pinned"), QStringLiteral("account") + QStringLiteral("pinned"), QStringLiteral("account"), + QStringLiteral("generated"), QStringLiteral("flat") }; if (!known.contains(it.key())) query.unknown.insert(it.key(), it.value()); @@ -550,6 +596,10 @@ bool Config::saveSavedQueries() const object.insert(QStringLiteral("pinned"), true); if (!query.account.isEmpty()) object.insert(QStringLiteral("account"), query.account); + if (query.isGenerated()) + object.insert(QStringLiteral("generated"), query.generated); + if (query.flat) + object.insert(QStringLiteral("flat"), true); for (auto it = query.unknown.begin(); it != query.unknown.end(); ++it) object.insert(it.key(), it.value()); array.append(object); @@ -572,6 +622,18 @@ bool Config::saveSavedQueries() const QString Config::resolvedQuery(const SavedQuery &query) const { + // Composed from the accounts every time it is asked for, which is the + // point: the answer follows the config rather than a copy of it taken when + // the entry was written. + if (query.isGenerated()) { + if (query.generated == QStringLiteral("sent")) + return allSentQuery(); + // An unknown generator was reported on load. Empty rather than the + // bare stored query, which for a generated entry is empty anyway and + // would otherwise run as "match everything". + return QString(); + } + if (query.account.isEmpty()) return query.query; diff --git a/src/config.h b/src/config.h index 4211b0c..b9ee9d6 100644 --- a/src/config.h +++ b/src/config.h @@ -122,6 +122,27 @@ struct SavedQuery /// user edits it. Resolve through Config::resolvedQuery(). QString account; + /// Names a builtin that COMPOSES this query from the accounts at run time, + /// rather than storing it. Empty for an ordinary query. + /// + /// "sent" is the only one today. Its query is built from every account's + /// `sent` key, so adding an account or correcting a folder name is a config + /// edit and nothing else; a stored copy of the same string would go stale + /// silently. That property is why Sent used to be hardcoded beside the + /// saved queries instead of living with them, which left one button on the + /// row that could not be reordered, renamed, unpinned or removed. + /// + /// Storing the GENERATOR rather than its output keeps both: the query stays + /// live, and the entry is an ordinary row the user owns. + QString generated; + + /// Lists messages rather than threads. Set for the sent view, where a + /// thread would fold every reply back into the conversation the user sent + /// one message into. + bool flat = false; + + bool isGenerated() const { return !generated.isEmpty(); } + /// Keys this build does not understand, preserved verbatim so a file /// written by a later version survives a save from this one. QJsonObject unknown; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f06b308..62f4751 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1673,40 +1673,34 @@ 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. QList<SavedQuery> unpinned; for (const SavedQuery &saved : m_config.savedQueries()) { + // A generator whose accounts configure nothing produces a button that + // always finds nothing. Skipped entirely, which is what the hardcoded + // Sent button did and is worth keeping. + if (saved.isGenerated() && m_config.resolvedQuery(saved).isEmpty()) + continue; + if (!saved.pinned) { unpinned.append(saved); 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")); connect(button, &QPushButton::clicked, this, [this, saved]() { runSavedQuery(saved); }); box->addWidget(button); } - // Sent sits with the saved queries and is not one: its query is COMPOSED - // from the accounts' `sent` keys at click time, so adding an account or - // correcting a folder name is a config edit and nothing else. A stored - // entry holding the same string would go stale silently, and could not - // narrow to the selected account the way this does through - // runCurrentQuery()'s existing scope wrap. - // - // Hidden entirely when no account configures a sent folder, rather than - // offering a button that always finds nothing. - if (!m_config.allSentQuery().isEmpty()) { - auto *sentButton = new QPushButton(tr("Sent"), row); - sentButton->setObjectName(QStringLiteral("sentButton")); - connect(sentButton, &QPushButton::clicked, this, [this]() { - m_queryEdit->setText(m_config.allSentQuery()); - // Flat for this query only. runCurrentQuery() clears it again for - // anything else, including the same query typed by hand, so the - // flag cannot outlive the button that set it. - runQuery(FlatResult::Yes); - }); - box->addWidget(sentButton); - } - // Everything above is left-aligned; the stretch here pushes what follows // to the right edge. The buttons are the row's content and read as a set, // while the overflow menu is a control over that set, so it sits apart @@ -1751,8 +1745,16 @@ void MainWindow::runSavedQuery(const SavedQuery &saved) if (index >= 0) m_accountBox->setCurrentIndex(index); - m_queryEdit->setText(saved.query); - runCurrentQuery(); + // A generated entry has no stored query: the text is composed from the + // accounts now, so what lands in the bar is what actually ran and the user + // can see and edit it. + m_queryEdit->setText(saved.isGenerated() ? m_config.resolvedQuery(saved) + : saved.query); + + // Flat for this query only. runQuery() sets the mode on EVERY run, so the + // flag cannot outlive the entry that asked for it, including for the same + // query typed by hand afterwards. + runQuery(saved.flat ? FlatResult::Yes : FlatResult::No); } void MainWindow::saveCurrentQuery() |
