aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.cpp66
-rw-r--r--src/config.h21
-rw-r--r--src/mainwindow.cpp50
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()