diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-03 15:41:56 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-03 15:41:56 +0200 |
| commit | f62ced3c2c85675e746bff7ef8aca5c75c9737e0 (patch) | |
| tree | c962e91fc502ec57d92df386e773033fe42fd17b | |
| parent | db7bbb81f02b52c2a1b299fe140abffab7e74642 (diff) | |
| download | qtmaildir-f62ced3c2c85675e746bff7ef8aca5c75c9737e0.tar.gz qtmaildir-f62ced3c2c85675e746bff7ef8aca5c75c9737e0.zip | |
feat: use the application iconfeature/qaction-menus
The icon was committed in a previous session and referenced nowhere: no
qrc, no .desktop entry, no setWindowIcon. It is wired up now, as a window
icon, a desktop entry, and install rules placing both into hicolor and
share/applications.
resources.qrc belongs to the executable rather than to qtmaildir_lib. A
qrc compiled into a static library registers itself from a global
initialiser, and the linker discards that object because nothing
references it: the build succeeded, qInitResources_resources() was
present in the .a, and QFile::exists(":/icons/qtmaildir.svg") still
returned false at runtime. Verified loading at 16, 32 and 64 pixels after
the move.
Toolbar and menu actions take icons from the system theme by their
standard names, so they match the rest of the desktop rather than
shipping bespoke art. A theme lacking one leaves that action as text,
which still works.
| -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> |
