summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-24 20:15:17 +0200
committerDanilo M. <danix@danix.xyz>2026-08-24 20:15:17 +0200
commit160c9121ec39d934330cf626978d02d0fa6d0610 (patch)
treea01e1aeb59e1ae0436785d9328ff939829465e99
parentafeacd7cb99db7fcf4d677dbe8dd52b10e1284e0 (diff)
downloadqtmaildir-160c9121ec39d934330cf626978d02d0fa6d0610.tar.gz
qtmaildir-160c9121ec39d934330cf626978d02d0fa6d0610.zip
feat(config): read the three signature keys
[compose] signature and signature_position, and a per-account signature that OVERRIDES the former. The account seeds the choice rather than owning it: the composer's switch keeps every signature reachable whichever account is selected, which is what keeps the note's "not tied to an account" constraint intact. The fallback is deliberately NOT resolved here. An account with no key of its own carries an empty string, so the composer can tell "says nothing" from "says none" and fall through itself. signature_position follows quote_position's shape exactly, reporting a present-but-malformed value rather than accepting it silently. Part of item 152. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEcn3u19xPqv6ggD15PG4c
-rw-r--r--src/config.cpp36
-rw-r--r--src/config.h23
-rw-r--r--tests/test_config.cpp73
3 files changed, 132 insertions, 0 deletions
diff --git a/src/config.cpp b/src/config.cpp
index 23c7364..534ba72 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -459,6 +459,17 @@ void Config::load(const QString &path)
account.sent =
settings.value(QStringLiteral("sent")).toString().trimmed();
+ // Optional, and a STARTING value rather than a binding: the composer's
+ // switch keeps every signature reachable whichever account is
+ // selected. Left empty when absent, so the composer can tell "this
+ // account says nothing" from "this account says none" and fall through
+ // to [compose] signature itself; resolving that here would collapse
+ // the two. Trimmed for the same reason as sent, above: a trailing
+ // space would be carried into a filename lookup and match nothing,
+ // which is invisible in a config file.
+ account.signature =
+ settings.value(QStringLiteral("signature")).toString().trimmed();
+
// Mandatory, unlike sent: Delete moves a file into this folder, so an
// account without one cannot delete at all. Trimmed for the same
// reason as sent, above.
@@ -547,6 +558,31 @@ void Config::load(const QString &path)
m_compose.sendHtml =
settings.value(QStringLiteral("send_html"), true).toBool();
+ // Trimmed for the same reason the account key is: it reaches a filename
+ // lookup, where a trailing space matches nothing invisibly.
+ m_compose.signature =
+ settings.value(QStringLiteral("signature")).toString().trimmed();
+
+ // The same shape as quote_position directly above: an absent key is
+ // silent and the struct default holds, but a PRESENT and malformed value
+ // is reported rather than silently accepted. value(key, default) alone
+ // would read "signature_position = abov" as above_quote.
+ const QString signaturePosition =
+ settings.value(QStringLiteral("signature_position"),
+ QStringLiteral("end"))
+ .toString().trimmed();
+ if (signaturePosition.compare(QStringLiteral("above_quote"),
+ Qt::CaseInsensitive) == 0) {
+ m_compose.signaturePosition = Signatures::Position::AboveQuote;
+ } else if (signaturePosition.compare(QStringLiteral("end"),
+ Qt::CaseInsensitive) == 0) {
+ m_compose.signaturePosition = Signatures::Position::End;
+ } else {
+ addProblem(tr("[compose] signature_position '%1' is not recognised; "
+ "expected end or above_quote. Using end.")
+ .arg(signaturePosition));
+ }
+
// Three numerics, all following the shape already established at
// message_zoom, toolbar_icon_size, mark_read_delay_ms and
// auto_sync_delay_ms elsewhere in this function: a QVariant, a checked
diff --git a/src/config.h b/src/config.h
index 4dcfbf1..02b4038 100644
--- a/src/config.h
+++ b/src/config.h
@@ -26,6 +26,7 @@
#include <QStringList>
#include "completionentry.h"
+#include "signatures.h"
class QSettings;
@@ -58,6 +59,18 @@ struct Account
/// one for the account that has none.
QString sent;
+ /// The signature seeded when composing from this account, by name.
+ ///
+ /// Optional, and it does not tie a signature to the account: the switch on
+ /// the composer's editor bar keeps every signature reachable whichever
+ /// account is selected. This is a STARTING value only, which is why the
+ /// user's "not tied to an account" constraint survives it (item 152).
+ ///
+ /// The fallback to [compose] signature is NOT resolved here. An account
+ /// with no key of its own carries an empty string and the composer falls
+ /// through, so the two values stay distinguishable.
+ QString signature;
+
/// The account's trash folder, relative to maildir.
///
/// MANDATORY, unlike `sent` and `drafts`. Delete moves a file into this
@@ -228,6 +241,16 @@ struct ComposeSettings
/// accounts. Falls through when it names an account that cannot send.
QString defaultAccount;
+ /// The signature seeded when the account carries none, by name. Empty
+ /// means no signature is seeded at all.
+ QString signature;
+
+ /// Where a newly inserted signature goes. End by default, which is the
+ /// user's own habit; above_quote exists because other clients offer the
+ /// choice, and the splice's quote-aware scan is needed for the guard
+ /// either way.
+ Signatures::Position signaturePosition = Signatures::Position::End;
+
qint64 attachmentWarnBytes = 26214400;
};
diff --git a/tests/test_config.cpp b/tests/test_config.cpp
index a5dce9a..17b8e1d 100644
--- a/tests/test_config.cpp
+++ b/tests/test_config.cpp
@@ -25,6 +25,7 @@
#include <QJsonObject>
#include "config.h"
#include "mailsync.h"
+#include "signatures.h"
class TestConfig : public QObject
{
@@ -134,6 +135,9 @@ private slots:
void garbageAttachmentWarnBytesIsRejectedNotZero();
void zeroOrNegativeAutosaveIntervalIsClamped();
void unrecognisedQuotePositionWarnsAndFallsBackToBelow();
+ void theSignatureKeysAreRead();
+ void anAccountSignatureOverridesTheComposeDefault();
+ void aMalformedSignaturePositionIsReportedAndFallsBack();
};
static QString writeIni(const QTemporaryDir &dir, const QString &body)
@@ -2577,5 +2581,74 @@ void TestConfig::unrecognisedQuotePositionWarnsAndFallsBackToBelow()
"an unrecognised quote_position was accepted silently");
}
+void TestConfig::theSignatureKeysAreRead()
+{
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral(
+ "[compose]\n"
+ "signature=work\n"
+ "signature_position=above_quote\n")));
+
+ QCOMPARE(config.compose().signature, QStringLiteral("work"));
+ QVERIFY2(config.compose().signaturePosition
+ == Signatures::Position::AboveQuote,
+ "signature_position=above_quote was not read");
+}
+
+void TestConfig::anAccountSignatureOverridesTheComposeDefault()
+{
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral(
+ "[compose]\n"
+ "signature=work\n"
+ "\n"
+ "[account.personal]\n"
+ "name=Test User\n"
+ "address=user@example.org\n"
+ "maildir=personal-mail\n"
+ "trash=Trash\n"
+ "signature=brief\n"
+ "\n"
+ "[account.other]\n"
+ "name=Test User\n"
+ "address=other@example.org\n"
+ "maildir=other-mail\n"
+ "trash=Trash\n")));
+
+ // The account SEEDS the choice; it does not own the signature. The key is
+ // a starting value and the switch keeps every signature reachable.
+ QCOMPARE(config.account(QStringLiteral("personal")).signature,
+ QStringLiteral("brief"));
+ // An account with no key of its own carries none, and the caller falls
+ // through to the [compose] default rather than this being resolved here.
+ QVERIFY2(config.account(QStringLiteral("other")).signature.isEmpty(),
+ "an account with no signature key must not inherit the "
+ "[compose] one: the composer resolves the fallback, not Config");
+ QCOMPARE(config.compose().signature, QStringLiteral("work"));
+}
+
+void TestConfig::aMalformedSignaturePositionIsReportedAndFallsBack()
+{
+ // Present and malformed is REPORTED, matching quote_position. A silent
+ // value(key, default) would accept "abov" as above_quote.
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral(
+ "[compose]\n"
+ "signature_position=abov\n")));
+
+ QVERIFY2(config.compose().signaturePosition == Signatures::Position::End,
+ "an unrecognised signature_position must still fall back to End");
+ bool reported = false;
+ for (const QString &problem : config.problems()) {
+ if (problem.contains(QStringLiteral("signature_position")))
+ reported = true;
+ }
+ QVERIFY2(reported,
+ "an unrecognised signature_position was accepted silently");
+}
+
QTEST_MAIN(TestConfig)
#include "test_config.moc"