diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-24 21:11:33 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-24 21:11:33 +0200 |
| commit | cf88c95aa5f16b918ebf207b3e323e44c525b440 (patch) | |
| tree | 144e5735bb2a62ee7a4b154cf7aa3c15004826d6 | |
| parent | 9b8a6da4ff39428ce22dc23e16fc48cc062bc01f (diff) | |
| download | qtmaildir-cf88c95aa5f16b918ebf207b3e323e44c525b440.tar.gz qtmaildir-cf88c95aa5f16b918ebf207b3e323e44c525b440.zip | |
feat(compose): the signature follows the account until it is chosen
A From: change re-seeds the signature from the newly selected account, and
stops doing so the moment the user picks one from the switch. Re-seeding
unconditionally is the one behaviour that can silently discard a deliberate
choice made a moment earlier; this is the shape send_html already uses.
seededSignatureName() reads the combo rather than the context, which
records where the composer opened and does not follow a change to it.
Part of item 152.
| -rw-r--r-- | src/composewindow.cpp | 27 | ||||
| -rw-r--r-- | tests/test_composewindow.cpp | 119 |
2 files changed, 141 insertions, 5 deletions
diff --git a/src/composewindow.cpp b/src/composewindow.cpp index 8236d79..94a3f46 100644 --- a/src/composewindow.cpp +++ b/src/composewindow.cpp @@ -406,8 +406,22 @@ void ComposeWindow::buildUi() for (QLineEdit *field : { m_to, m_cc, m_bcc, m_subject }) connect(field, &QLineEdit::textChanged, this, &ComposeWindow::markDirty); connect(m_sendHtml, &QCheckBox::toggled, this, &ComposeWindow::markDirty); - connect(m_from, &QComboBox::currentIndexChanged, this, - &ComposeWindow::markDirty); + connect(m_from, &QComboBox::currentIndexChanged, this, [this]() { + markDirty(); + // The account SEEDS the signature, so a change to it re-seeds. It + // stops the moment the user picks one: re-seeding unconditionally is + // the one behaviour that can silently discard a deliberate choice + // made a moment earlier. Same shape as send_html, which seeds from + // context and is then left alone. + if (m_signatureChosen) + return; + const QString seeded = seededSignatureName(); + if (!Signatures::names(m_signatureDir).contains(seeded)) { + applySignature(QString()); + return; + } + applySignature(seeded); + }); } void ComposeWindow::buildFormatToolbar() @@ -708,9 +722,12 @@ QStringList ComposeWindow::knownSignatures() const QString ComposeWindow::seededSignatureName() const { - // The account SEEDS, it does not bind: this is a starting value, and the - // switch keeps every signature reachable whichever account is selected. - const Account account = m_config.account(m_context.accountKey); + // The COMBO, not m_context: the context records where the composer opened + // and does not follow a From: change, so reading it would seed the + // original account's signature for ever. + const QString key = m_from->currentData().toString(); + const Account account = + m_config.account(key.isEmpty() ? m_context.accountKey : key); if (!account.signature.isEmpty()) return account.signature; return m_config.compose().signature; diff --git a/tests/test_composewindow.cpp b/tests/test_composewindow.cpp index 04d3115..8221ce3 100644 --- a/tests/test_composewindow.cpp +++ b/tests/test_composewindow.cpp @@ -17,6 +17,7 @@ */ #include <QtTest> +#include <QComboBox> #include <QDir> #include <QFile> #include <QMenu> @@ -43,6 +44,8 @@ private slots: void aResumedDraftSeedsNoSignature(); void anUnknownSignatureNameSeedsNothing(); void theSwitchListsEveryFileAndNone(); + void changingTheAccountFollowsItsSignature(); + void changingTheAccountStopsFollowingOnceTheSwitchIsUsed(); private: /// A config pointing at a signatures directory holding \p files, with one @@ -216,5 +219,121 @@ void TestComposeWindow::theSwitchListsEveryFileAndNone() QCOMPARE(button->menu()->actions().size(), 3); } +void TestComposeWindow::changingTheAccountFollowsItsSignature() +{ + for (const auto &entry : + QList<QPair<QString, QString>>{ + { QStringLiteral("work.md"), QStringLiteral("Work sig") }, + { QStringLiteral("home.md"), QStringLiteral("Home sig") } }) { + QFile file(m_signatureDir + QStringLiteral("/") + entry.first); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + file.write(entry.second.toUtf8()); + file.close(); + } + + const QString path = m_dir->path() + QStringLiteral("/qtmaildir.conf"); + { + QFile file(path); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&file); + out << "[account.work]\n" + << "name = Someone\naddress = someone@example.org\n" + << "maildir = work\nsend_command = /bin/cat\n" + << "signature = work\n" + << "\n[account.home]\n" + << "name = Someone\naddress = other@example.org\n" + << "maildir = home\nsend_command = /bin/cat\n" + << "signature = home\n"; + } + Config config; + config.load(path); + + ComposeContext context; + context.kind = ComposeContext::Kind::New; + context.accountKey = QStringLiteral("work"); + + ComposeWindow window(context, config, m_dir->path()); + window.setSignatureDir(m_signatureDir); + window.seedSignature(); + + auto *body = window.findChild<QPlainTextEdit *>(QStringLiteral("body")); + auto *from = window.findChild<QComboBox *>(QStringLiteral("from")); + QVERIFY(body); + QVERIFY(from); + QVERIFY(body->toPlainText().contains(QStringLiteral("Work sig"))); + + // Select the other account by its key, never by index: the order of the + // combo is the config's and an index assertion would pass on the wrong one. + const int home = from->findData(QStringLiteral("home")); + QVERIFY(home >= 0); + from->setCurrentIndex(home); + + QVERIFY(body->toPlainText().contains(QStringLiteral("Home sig"))); + QVERIFY(!body->toPlainText().contains(QStringLiteral("Work sig"))); +} + +void TestComposeWindow::changingTheAccountStopsFollowingOnceTheSwitchIsUsed() +{ + for (const auto &entry : + QList<QPair<QString, QString>>{ + { QStringLiteral("work.md"), QStringLiteral("Work sig") }, + { QStringLiteral("home.md"), QStringLiteral("Home sig") }, + { QStringLiteral("chosen.md"), QStringLiteral("Chosen sig") } }) { + QFile file(m_signatureDir + QStringLiteral("/") + entry.first); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + file.write(entry.second.toUtf8()); + file.close(); + } + + const QString path = m_dir->path() + QStringLiteral("/qtmaildir.conf"); + { + QFile file(path); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&file); + out << "[account.work]\n" + << "name = Someone\naddress = someone@example.org\n" + << "maildir = work\nsend_command = /bin/cat\n" + << "signature = work\n" + << "\n[account.home]\n" + << "name = Someone\naddress = other@example.org\n" + << "maildir = home\nsend_command = /bin/cat\n" + << "signature = home\n"; + } + Config config; + config.load(path); + + ComposeContext context; + context.kind = ComposeContext::Kind::New; + context.accountKey = QStringLiteral("work"); + + ComposeWindow window(context, config, m_dir->path()); + window.setSignatureDir(m_signatureDir); + window.seedSignature(); + + auto *body = window.findChild<QPlainTextEdit *>(QStringLiteral("body")); + auto *from = window.findChild<QComboBox *>(QStringLiteral("from")); + auto *button = + window.findChild<QToolButton *>(QStringLiteral("signatureSwitch")); + QVERIFY(body); + QVERIFY(from); + QVERIFY(button); + + // The user picks one deliberately. + for (QAction *action : button->menu()->actions()) { + if (action->data().toString() == QStringLiteral("chosen")) + action->trigger(); + } + QVERIFY(body->toPlainText().contains(QStringLiteral("Chosen sig"))); + + const int home = from->findData(QStringLiteral("home")); + QVERIFY(home >= 0); + from->setCurrentIndex(home); + + // The deliberate choice survives the account change. Overwriting it is + // the one behaviour that can silently discard something the user just did. + QVERIFY(body->toPlainText().contains(QStringLiteral("Chosen sig"))); + QVERIFY(!body->toPlainText().contains(QStringLiteral("Home sig"))); +} + QTEST_MAIN(TestComposeWindow) #include "test_composewindow.moc" |
