| Age | Commit message (Collapse) | Author | Files | Lines |
|
Pressing Return in the query bar moved focus to the thread list and left
the query unrun, so a query typed at the keyboard could not be executed
at all.
Return is bound to open_thread as a Qt::WindowShortcut, and a shortcut is
dispatched before the focused widget ever sees the key. Qt withholds a
plain-LETTER shortcut from an editable widget, which is why every other
binding in the map was safe here, but Return is not a letter and gets no
such protection: the action fired from inside the bar, its handler called
setFocus() on the thread list, and QLineEdit::returnPressed was never
emitted.
Accept the ShortcutOverride for Return and Enter on the query bar, which
tells Qt the focused widget wants the key as ordinary input and stops the
shortcut being dispatched. Narrow by design, one widget and one key, so
open_thread keeps working everywhere else in the window.
Three earlier hypotheses were tested and disproven before this one, and
two synthetic probes wrongly reported the binding as harmless: real input
sends ShortcutOverride first and only dispatches the shortcut if nothing
claims it, while QTest::keyClick skips that round trip entirely. The new
test drives the override exchange rather than the keystroke and fails
against the old code. The comment claiming no filter was needed said the
letter rule covered this case; it did not, and it now says so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
An attachment was only discoverable by opening the thread. A narrow
leftmost column now marks the threads that carry one.
No new worker query is involved: notmuch applies the "attachment" tag
while indexing, so ThreadSummary already holds what this needs. The
marker is a glyph rather than an icon resource, which ships no new asset
and inherits the row font, so it strikes through with a doomed thread
like every other cell. It falls back to "*" where the system font cannot
draw U+1F4CE, since an unrenderable codepoint reads as breakage rather
than as a marker.
Two silent Qt behaviours had to be handled, both found by probe:
QHeaderView::restoreState() returns true for a blob saved against fewer
columns and applies the old widths shifted one place right. Adding a
column in front would therefore have mangled every existing saved
layout with no error to detect it by. The column count is now stored
beside the blob and a mismatch discards it, so the widths reset once on
upgrade instead of landing on the wrong columns.
QHeaderView's default minimumSectionSize is 58px on this platform, and
setColumnWidth() clamps to it without reporting the smaller value back,
so the column could not be narrow at all until it was lowered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Zoom was Chromium's, not the application's: the web view handled the
keys natively and never told anyone, so there was no value to save.
qtmaildir now owns it. Zoom in, out and reset are real actions, in the
View menu and rebindable through [keys], and the factor is persisted to
the UI state file. Ctrl+wheel over the body zooms and Ctrl+middle-click
resets, both filtered by ancestry from an application-level filter: the
events are delivered to an internal QQuickWidget the web view creates
lazily, so a filter on the view itself never sees them.
The factor is clamped to 0.5 - 3.0, and NaN, infinity, zero and negative
values fall back to 1.0, since a corrupt state file must not be able to
leave the pane unreadable with no visible way back.
Both risks the plan flagged turned out not to exist, verified by probe
rather than assumed. The application QAction wins over the web view's
native zoom key, so the tracked factor cannot diverge from what is on
screen. And the factor survives setHtml(), so the web view is the single
source of truth and needs no reapply per render.
A third finding is worth recording because it produced a wrong fix
first. A probe using QTest::keyClick() reported Ctrl++ as a dead
binding, and a test was written asserting that. Both were wrong: Ctrl++
is exactly what the '+' key emits on an Italian layout, confirmed
against the real keyboard, and it is the shipped default. Whether a
symbol needs Shift is a property of the layout, not of Qt, and
keyClick() reproduces neither. The test now only checks that every
default parses, and the comment in defaultBindings() says not to
re-derive this from synthetic input.
Ctrl+= is a second binding for reset, skipped when [keys] gives it to
something else.
Also fixes a pre-existing bug the new config key exposed. [general]
entries were read as "general/<key>", which matches nothing: QSettings'
INI backend treats a section literally named [general] as its own
fallback section and strips the prefix. notmuch_config had therefore
never worked. Both keys are now read without it; the file format is
unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Resizing the window, the splitter or a thread-list column was undone by
the next launch. State now round-trips through a separate settings file
at ~/.local/state/qtmaildir/uistate.conf, written on close and read at
startup.
The state file is deliberately not the user's config: a base64 geometry
blob does not belong in a hand-edited file, and rewriting that file on
exit would drop its comments and key order, which QSettings does not
preserve.
Two details that are easy to get wrong:
QStandardPaths::StateLocation appends both the organization and the
application name, and both are "qtmaildir" here, so it resolves to
~/.local/state/qtmaildir/qtmaildir. The path is built from
GenericStateLocation instead, matching Config::defaultPath().
Restore runs after buildMenus() rather than at the end of buildUi():
QMainWindow::restoreState() matches toolbars by object name and silently
drops the position of one that does not exist yet.
Every restore is conditional on a non-empty blob, so a missing or
rejected state file leaves the built-in defaults instead of producing a
zero-size window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Actions were a QHash of std::function dispatched by an event filter,
which nothing could put in a menu. They are QActions now, bound from
KeyMap so a [keys] override reaches the menus as well as the keyboard.
Menu bar covers every action; the toolbar carries only Sync, Archive,
Delete and Undo. Help > Keyboard shortcuts is generated from the actions,
so it shows what the keys really do rather than a copy that drifts.
spam and load_remote gained defaults, having been unreachable without a
hand-written binding.
The event filter is gone. Probing showed QAction shortcuts are dispatched
before the focused widget sees the key, so they beat QAbstractItemView's
type-to-search without one, and Qt already suppresses plain-letter
shortcuts while an editable widget has focus. Dropping the filter's
blanket guard also lets Ctrl+Q work while the query bar has focus.
registeredActionNames() is derived from the actions rather than
hand-maintained, so the two drift tests it needed are replaced by checks
that a configured binding reaches its action.
No confirmation dialogs: tag mutations still answer to undo.
|
|
Confirmed with the maintainer as v2-only rather than v2-or-later. LICENSE is
the official text from gnu.org. Every file under src/ and tests/ carries the
matching notice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Wires the worker thread, thread list, message pane, sync process and undo
stack together, and replaces the placeholder main() with real startup:
custom URL schemes registered before QApplication, a libnotmuch ABI check,
and config loading.
Four fixes against the drafted version:
- onWorkerError() only set a status label. Its own comment elsewhere
claimed it reverted the optimistic update, and the spec requires that;
it did not, so a rejected write left the list showing a tag the database
never received. The pending change is now recorded and rolled back, and
a confirmed tagsApplied clears it so a later unrelated error cannot undo
a write that succeeded.
- runCurrentQuery() cleared the model but left the undo stack pointing at
rows that no longer exist. Undoing after a new query would have written
to the database while the visible list stayed put. The stack is cleared
with the model.
- m_currentMessages was assigned on every thread load and never read.
Removed.
- buildUi() connected sync output to m_syncLog and errors to m_statusLabel
before either existed. Both are constructed before the wiring now.
cidPrefix generation lives here, this being its only producer in the
application, and is pinned by tests: it must never contain '!' and must be
distinct per message, which are the invariants the cid: namespacing rests
on. A second test holds registeredActionNames() against
KeyMap::knownActions(), since those two hand-maintained lists drifting
either way silently breaks a user's key binding. Mutation-verified that
dropping an action fails the test by name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|