diff options
| -rw-r--r-- | CHANGELOG.md | 26 | ||||
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | README.md | 35 | ||||
| -rw-r--r-- | assets/qtmaildir.desktop | 13 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 40 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 16 | ||||
| -rw-r--r-- | src/main.cpp | 8 | ||||
| -rw-r--r-- | src/resources.qrc | 6 |
8 files changed, 141 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ff08b..c78cb94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,22 @@ point at which they are stable. ### Added +- Tags render as coloured chips instead of text in a column. The account tag + sits in front of the subject in the thread list, and the functional tags fill + a single row under the message pane, with anything that does not fit + collapsing into a `+N` chip whose tooltip names the rest. +- `[tagcolors]` config group. Colours resolve by exact tag first, then by + top-level prefix, so one `shopping` entry covers `shopping/amazon` and + `shopping/nike` while `shopping/amazon` can still override its own. Built-in + defaults cover the usual state tags; anything unconfigured gets a stable + colour derived from its name. +- `color` and `label` keys in an account stanza, setting the account chip's + fill and its text. `label` shortens a long key for display only and renames + nothing in notmuch; unset falls back to the key. +- The application icon is now used: window icon, a `.desktop` entry, and + install rules placing both into `hicolor` and `share/applications`. +- Toolbar and menu actions carry icons from the system theme, falling back to + text where a theme lacks one. - Menu bar covering every action: File, Edit, Message, View and Help. - Toolbar with the frequent subset, Sync, Archive, Delete and Undo. - **Help > Keyboard shortcuts**, listing the current bindings. Generated from @@ -29,8 +45,14 @@ point at which they are stable. every column. The tag change was already applied, but `Tags` sat after the stretching `Subject` column and was pushed off-screen, so Delete looked like it had done nothing. -- Thread list columns reordered to Tags, Date, From, Subject. Subject stretches - and is now last, so no column can be pushed out of view. +- Thread list columns are Date, From and Subject, all resizable. The tags + column is gone: spelling out a dozen tags per row consumed most of the list's + width. Widening past the viewport scrolls horizontally rather than squeezing + the other columns. +- Hierarchical tags in `[tagcolors]` were silently ignored. QSettings treats + `/` in a key as a group separator, so `shopping/amazon` becomes a nested key + that `childKeys()` never returns, and every tag containing a `/` fell through + to its prefix. - Three default bindings never fired. Typing a capital sends `Shift`+the key, but `N`, `F` and `G` were stored as the unshifted key, which no keystroke produces, leaving `toggle_unread`, `flag` and `sync` dead. A bare capital in diff --git a/CMakeLists.txt b/CMakeLists.txt index ed9db9c..7b73a7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(qtmaildir VERSION 0.1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets WebEngineWidgets Test) @@ -91,6 +91,8 @@ name = Your Name address = you@example.org maildir = work-mail ; relative to notmuch's database.path drafts = Drafts ; recorded for v2; unused today +label = W ; optional chip text; defaults to the key +color = #2f6fa8 ; optional chip colour; generated when unset [account.personal] name = Your Name @@ -98,6 +100,13 @@ address = you@example.net maildir = personal drafts = Drafts +[tagcolors] +; Optional. Colours resolve by exact tag first, then by top-level prefix, so +; one entry covers a whole hierarchy. +shopping = #3366cc ; also colours shopping/amazon, shopping/nike, ... +shopping/amazon = #ff9900 ; ... unless the exact tag overrides it +work = #cc4444 + [queries] Inbox = tag:inbox Unread = tag:unread @@ -114,6 +123,32 @@ Saved-query buttons appear in alphabetical order rather than file order: QSettings returns keys sorted, and preserving file order would mean hand-rolling an INI parser. +## Tags + +Tags render as coloured chips, and fall into two kinds. + +**Account tags** (`account-<key>`, matching an `[account.<key>]` stanza) say +which mailbox a thread arrived in. They appear as a chip in front of the +subject in the thread list, coloured by that account's `color` key and labelled +by its `label` key. `label` changes the chip text only; the notmuch tag is +never renamed, so queries and external tagging are unaffected. + +**Functional tags** say what state a thread is in. They fill one row under the +message pane, sorted, with whatever does not fit collapsing into a `+N` chip +whose tooltip lists the rest. Colours come from `[tagcolors]`, falling back to +built-in defaults for the usual state tags (`flagged`, `unread`, `deleted`, +`spam`, `attachment`, `replied`, and others), and finally to a colour derived +from the tag name so no chip is ever unstyled. + +Lookup is exact tag first, then top-level prefix. One `shopping` entry +therefore covers `shopping/amazon` and `shopping/nike`, while a +`shopping/amazon` entry still overrides its own. + +Note that a `/` in an INI key is a group separator to QSettings, so +`shopping/amazon = #ff9900` is stored as a nested key and written to the file +as `shopping\amazon`. It is read back correctly; the escaping is QSettings' +own. + ## Keybindings Defaults, all rebindable through `[keys]`: diff --git a/assets/qtmaildir.desktop b/assets/qtmaildir.desktop new file mode 100644 index 0000000..98e435b --- /dev/null +++ b/assets/qtmaildir.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=qtmaildir +GenericName=Mail Reader +Comment=Read and organize a local notmuch-indexed Maildir +Exec=qtmaildir +Icon=qtmaildir +Terminal=false +Categories=Network;Email;Qt; +Keywords=mail;email;notmuch;maildir; +StartupNotify=true +StartupWMClass=qtmaildir diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md index 1e08bb1..915c8cb 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md @@ -44,8 +44,9 @@ taking that too literally. | 8 | No buttons or menu entries for archive, undo, etc | discoverability | M | **done** | | 9 | No in-app view of configured shortcuts | discoverability | S | **done** | | 10 | Reaching an account's inbox takes two steps | workflow | S | open | -| 11 | Icon, `.desktop` file, SlackBuild | packaging | M | open | +| 11 | Icon, `.desktop` file, SlackBuild | packaging | M | **partly done**: icon and `.desktop` landed, SlackBuild open | | 13 | No visual feedback that an action stuck | feedback | S | **done** | +| 14 | Tag column unreadable, tags need another home | presentation | M | **done** | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -398,6 +399,43 @@ and inspected: normal, unread, deleted, spam, and deleted-plus-unread rows. --- +## 14. Tag column unreadable + +**Observed:** with tags spelled out per row the column ran to 500 pixels of +mostly repeated text ("account-privateemail-danilo.macri attachment flagged +inbox passed replied"), dominated by the account prefix, and consumed most of +the list's width. + +**Cause:** presentation, not data. 96 tags in this database, many hierarchical +(`shopping/amazon`, `mailing-list/SBo`), rendered as a joined string. + +**Approach:** the column is gone. Tags now render as coloured chips in two +places, split by taxonomy: + +- The **account tag** says which mailbox a thread came from. It draws as a chip + in front of the subject, coloured and labelled from its own `[account.<key>]` + stanza via new `color` and `label` keys. `label` is display-only; the notmuch + tag is never renamed. +- **Functional tags** say what state a thread is in. They fill a single row + under the message pane, with overflow collapsing into a `+N` chip whose + tooltip lists the hidden ones. A single row keeps the message area from + shifting between threads with different tag counts. + +Colours resolve exact tag first, then top-level prefix, so one `shopping` entry +covers the hierarchy without listing all 96. Unconfigured tags fall back to a +hash of the name, stable so a chip never changes colour as the list scrolls. + +**Defect found while building:** QSettings treats `/` in a key as a group +separator, so `shopping/amazon` becomes a nested key that `childKeys()` never +returns. Reading `[tagcolors]` with `childKeys()` silently dropped every +hierarchical tag, and each fell through to its prefix colour. Fixed by reading +`allKeys()`, with a regression test. The same gotcha is already documented in +`CLAUDE.md` for `[account.work]` section names. + +**Deferred:** clicking a chip to search that tag. Display only for now. + +--- + ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e4cea8..26cb37c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,9 @@ add_library(qtmaildir_lib STATIC htmlbuilder.cpp cidschemehandler.cpp notmuchworker.cpp + tagchip.cpp + tagcolors.cpp + tagstrip.cpp threadlistmodel.cpp mailsync.cpp threadcidmap.cpp @@ -20,7 +23,18 @@ target_include_directories(qtmaildir_lib target_link_libraries(qtmaildir_lib PUBLIC Qt6::Widgets Qt6::WebEngineWidgets PkgConfig::GMIME ${NOTMUCH_LIBRARY}) -add_executable(qtmaildir main.cpp) +# resources.qrc belongs to the executable, not to the static library. A qrc +# compiled into a .a registers itself from a global initialiser, and the linker +# drops that object because nothing references it, so the resource silently +# fails to exist at runtime. +add_executable(qtmaildir main.cpp resources.qrc) target_link_libraries(qtmaildir PRIVATE qtmaildir_lib) install(TARGETS qtmaildir RUNTIME DESTINATION bin) + +# The icon goes into the hicolor theme under its scalable directory, which is +# where a desktop environment looks for the Icon= name in the .desktop entry. +install(FILES ${CMAKE_SOURCE_DIR}/assets/icons/qtmaildir.svg + DESTINATION share/icons/hicolor/scalable/apps) +install(FILES ${CMAKE_SOURCE_DIR}/assets/qtmaildir.desktop + DESTINATION share/applications) diff --git a/src/main.cpp b/src/main.cpp index 4d3ed0a..231594f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ */ #include <QApplication> +#include <QIcon> #include <QMessageBox> #include <QWebEngineUrlScheme> @@ -76,6 +77,13 @@ int main(int argc, char *argv[]) app.setOrganizationName(QStringLiteral("qtmaildir")); app.setApplicationVersion(QStringLiteral(QTMAILDIR_VERSION)); + // Compiled in rather than read from disk, so the icon is there whether or + // not the app was installed. setDesktopFileName() is what lets a Wayland + // compositor match the window to its .desktop entry, which is where the + // taskbar icon really comes from there. + app.setWindowIcon(QIcon(QStringLiteral(":/icons/qtmaildir.svg"))); + app.setDesktopFileName(QStringLiteral("qtmaildir")); + // Fail loudly on an ABI mismatch rather than crashing later. if (LIBNOTMUCH_MAJOR_VERSION < 5) { QMessageBox::critical(nullptr, QObject::tr("qtmaildir"), diff --git a/src/resources.qrc b/src/resources.qrc new file mode 100644 index 0000000..7bdb592 --- /dev/null +++ b/src/resources.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource prefix="/"> + <file alias="icons/qtmaildir.svg">../assets/icons/qtmaildir.svg</file> + </qresource> +</RCC> |
