summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 15:41:33 +0200
committerDanilo M. <danix@danix.xyz>2026-08-03 15:41:33 +0200
commitdb7bbb81f02b52c2a1b299fe140abffab7e74642 (patch)
tree01cd604bc471cdbf11d22e85f6b0f24159ebc509
parentcd0be6dc5e4741c2ad180d6bf6a275f000ab943a (diff)
downloadqtmaildir-db7bbb81f02b52c2a1b299fe140abffab7e74642.tar.gz
qtmaildir-db7bbb81f02b52c2a1b299fe140abffab7e74642.zip
feat: let an account set its chip colour and label
Two optional keys in an [account.<key>] stanza. color fills the chip, label sets its text. Both belong to the account rather than to [tagcolors] because an account tag is a different taxonomy: which mailbox a thread arrived in, not what state it is in. label is display only. "account-privateemail-danilo.macri" is a lot of row for one bit of information, but the notmuch tag is never renamed, so existing queries and external tagging are unaffected. Unset falls back to the account key, and an empty label is ignored rather than rendering a blank chip.
-rw-r--r--src/config.cpp17
-rw-r--r--src/config.h10
-rw-r--r--src/mainwindow.cpp50
-rw-r--r--src/mainwindow.h2
4 files changed, 77 insertions, 2 deletions
diff --git a/src/config.cpp b/src/config.cpp
index de6a783..f21bba9 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -89,6 +89,23 @@ void Config::load(const QString &path)
account.address = settings.value(QStringLiteral("address")).toString();
account.maildir = settings.value(QStringLiteral("maildir")).toString();
account.drafts = settings.value(QStringLiteral("drafts")).toString();
+
+ // Both optional, and both describe this account's chip in the thread
+ // list. An account tag is a different taxonomy from a functional one,
+ // saying which mailbox a thread arrived in rather than what state it
+ // is in, so these live here rather than in [tagcolors].
+ account.label = settings.value(QStringLiteral("label")).toString();
+
+ const QString colour = settings.value(QStringLiteral("color")).toString();
+ if (!colour.isEmpty()) {
+ account.color = QColor(colour);
+ if (!account.color.isValid()) {
+ addProblem(
+ QStringLiteral("Account '%1' has an unparseable color '%2'; "
+ "using a generated one.")
+ .arg(account.key, colour));
+ }
+ }
settings.endGroup();
if (!account.isValid()) {
diff --git a/src/config.h b/src/config.h
index 270b780..943cbd8 100644
--- a/src/config.h
+++ b/src/config.h
@@ -18,6 +18,7 @@
#pragma once
+#include <QColor>
#include <QList>
#include <QString>
#include <QStringList>
@@ -33,6 +34,15 @@ struct Account
QString maildir; ///< Relative to notmuch's database.path.
QString drafts; ///< Unused in v1; send is v2.
+ /// Chip colour in the thread list. Invalid when unset, in which case one
+ /// is generated from the account tag's name.
+ QColor color;
+
+ /// Text shown on the chip. Empty falls back to the key, which can be long:
+ /// "privateemail-danilo.macri" is a lot of row for one bit of information.
+ /// This renames nothing in notmuch, only what the chip displays.
+ QString label;
+
bool isValid() const { return !key.isEmpty() && !maildir.isEmpty(); }
/// Restricts a notmuch query to this account's subtree.
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index ba690b4..48b475c 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -42,6 +42,7 @@
#include "messageview.h"
#include "mimeparser.h"
#include "notmuchworker.h"
+#include "tagchip.h"
#include "threadlistmodel.h"
#include "version.h"
@@ -73,6 +74,14 @@ MainWindow::MainWindow(const Config &config, QWidget *parent)
{
QSettings settings(Config::defaultPath(), QSettings::IniFormat);
m_keyMap.loadOverrides(settings);
+ m_tagColors.load(settings);
+ }
+
+ // An account's chip colour comes from its own stanza, since an account tag
+ // is a different taxonomy from a functional one.
+ for (const Account &account : m_config.accounts()) {
+ m_tagColors.setAccountColour(account.key, account.color);
+ m_tagColors.setAccountLabel(account.key, account.label);
}
buildUi();
@@ -163,6 +172,7 @@ void MainWindow::buildUi()
// Thread list and message pane.
m_model = new ThreadListModel(this);
+ m_model->setTagColors(&m_tagColors);
m_threadView = new QTableView(central);
m_threadView->setModel(m_model);
m_threadView->setSelectionBehavior(QAbstractItemView::SelectRows);
@@ -176,6 +186,10 @@ void MainWindow::buildUi()
m_threadView->horizontalHeader()->setSectionResizeMode(
column, QHeaderView::Interactive);
}
+
+ // The subject cell carries the account chip in front of its text.
+ m_threadView->setItemDelegateForColumn(ThreadListModel::SubjectColumn,
+ new SubjectDelegate(this));
// Widening a column past the viewport scrolls rather than squeezing the
// others. Per-pixel so the scroll does not jump a whole column at a time.
m_threadView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@@ -183,7 +197,6 @@ void MainWindow::buildUi()
// Starting widths only; a drag overrides them, and they are what the
// saved-widths item will persist.
- m_threadView->setColumnWidth(ThreadListModel::TagsColumn, 160);
m_threadView->setColumnWidth(ThreadListModel::DateColumn, 130);
m_threadView->setColumnWidth(ThreadListModel::AuthorsColumn, 180);
m_threadView->setColumnWidth(ThreadListModel::SubjectColumn, 520);
@@ -193,6 +206,7 @@ void MainWindow::buildUi()
this, &MainWindow::onThreadSelected);
m_messageView = new MessageView(central);
+ m_messageView->setTagColors(&m_tagColors);
connect(m_messageView, &MessageView::statusMessage,
this, [this](const QString &text) { m_statusLabel->setText(text); });
@@ -356,6 +370,28 @@ void MainWindow::buildMenus()
auto *about = helpMenu->addAction(tr("&About"));
connect(about, &QAction::triggered, this, &MainWindow::showAbout);
+ // Standard names from the icon theme, so the buttons match the rest of the
+ // desktop rather than shipping bespoke art. A theme that lacks one leaves
+ // that action with text alone, which still works.
+ const QHash<QString, QString> themeIcons = {
+ { QStringLiteral("sync"), QStringLiteral("mail-receive") },
+ { QStringLiteral("archive"), QStringLiteral("mail-mark-read") },
+ { QStringLiteral("delete"), QStringLiteral("edit-delete") },
+ { QStringLiteral("undo"), QStringLiteral("edit-undo") },
+ { QStringLiteral("spam"), QStringLiteral("mail-mark-junk") },
+ { QStringLiteral("flag"), QStringLiteral("mail-mark-important") },
+ { QStringLiteral("quit"), QStringLiteral("application-exit") },
+ { QStringLiteral("focus_query"), QStringLiteral("edit-find") },
+ };
+ for (auto it = themeIcons.cbegin(); it != themeIcons.cend(); ++it) {
+ QAction *action = m_actions.value(it.key());
+ if (!action)
+ continue;
+ const QIcon icon = QIcon::fromTheme(it.value());
+ if (!icon.isNull())
+ action->setIcon(icon);
+ }
+
// The frequent subset only. A toolbar holding every action is as
// unreadable as no toolbar.
auto *toolBar = addToolBar(tr("Main"));
@@ -543,7 +579,9 @@ void MainWindow::onThreadSelected(const QModelIndex &current,
if (!current.isValid())
return;
- m_currentThreadId = m_model->threadAt(current.row()).threadId;
+ const ThreadSummary thread = m_model->threadAt(current.row());
+ m_currentThreadId = thread.threadId;
+ m_messageView->setTags(thread.tags);
QMetaObject::invokeMethod(m_worker, "loadThread", Qt::QueuedConnection,
Q_ARG(QString, m_currentThreadId),
Q_ARG(QString, m_lastQuery),
@@ -665,6 +703,14 @@ void MainWindow::sendThreadTagChange(const QStringList &threadIds,
for (const QString &threadId : threadIds)
m_model->applyTagChange(threadId, add, remove);
+ // The strip shows the open thread's tags, so it has to follow a change to
+ // that thread rather than waiting for the next selection.
+ if (threadIds.contains(m_currentThreadId)) {
+ const QModelIndex current = m_threadView->currentIndex();
+ if (current.isValid())
+ m_messageView->setTags(m_model->threadAt(current.row()).tags);
+ }
+
m_pendingThreadIds = threadIds;
m_pendingChange = TagChange{ {}, add, remove, description };
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 30a128a..4445894 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -28,6 +28,7 @@
#include "config.h"
#include "keymap.h"
+#include "tagcolors.h"
#include "types.h"
class QAction;
@@ -103,6 +104,7 @@ private:
Config m_config;
KeyMap m_keyMap;
+ TagColors m_tagColors;
QThread m_workerThread;
NotmuchWorker *m_worker = nullptr;