diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-04 18:16:31 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 18:16:31 +0200 |
| commit | 987a9e728995cbbfc77e470db72d552ebe096ba2 (patch) | |
| tree | 595164649d7fb49b73b98240382e6df6982d9123 /src/mainwindow.h | |
| parent | 1407f70352c05d59fa2e1bb7c59b048b767972e5 (diff) | |
| download | qtmaildir-987a9e728995cbbfc77e470db72d552ebe096ba2.tar.gz qtmaildir-987a9e728995cbbfc77e470db72d552ebe096ba2.zip | |
feat(ui): make multi-select discoverable and stop it opening threads
Multi-select already worked by Ctrl+click and Shift+click, but nothing in
the UI said so and every tag action was keyboard-only, so the Ctrl+T tag
dialog could not be reached with a mouse at all.
Adds a select_all action on Ctrl+A, registered like every other action so
it reaches the Edit menu, the shortcut reference and [keys]; a right-click
menu on the thread list built from the same QActions rather than parallel
copies; a selection count in the status bar, which is the part that
actually teaches the feature by acknowledging a selection while it is
being built; and a note in the shortcut dialog for the mouse gestures,
which are view behaviour and cannot appear in the generated table.
A selection gesture must not open mail or mutate it. Selecting several
rows now blanks the message pane and cancels any pending mark-read,
rather than rendering each row swept through and queueing it to be marked
read.
Two Qt behaviours shaped this, both established by probe rather than from
memory:
- selectAll() emits no currentRowChanged at all and leaves the current
index invalid.
- currentRowChanged is emitted BEFORE the selection model is updated.
The second one caused two distinct faults. Collapsing a multi-row
selection back to one row reported the old count, so the guard swallowed
the load and the pane stayed blank; that case is handled in
onSelectionChanged, which sees the true count. And a Ctrl+click taking
the selection from one row to two also reported one, so the thread was
loaded, blanked, and then painted back when the queued reply returned
from the worker. By the third row the id was already cleared and the
reply was discarded, which is why the fault presented as an off-by-one in
the threshold rather than as a race.
Tests cover the synchronous half. The late-reply guard has no test:
MainWindow in tests has no worker, so threadLoaded never fires and the
repaint cannot be reproduced in process. Verified by hand instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/mainwindow.h')
| -rw-r--r-- | src/mainwindow.h | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/src/mainwindow.h b/src/mainwindow.h index d1f7b1c..933c71b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -33,6 +33,7 @@ class QAction; class QLineEdit; +class QMenu; class QTableView; class QLabel; class QPushButton; @@ -60,6 +61,12 @@ public: /// really registered. QStringList registeredActionNames() const; + /// The thread currently shown in the message pane, empty when it is blank. + /// + /// Empty is what "the pane is blanked" means internally: a late-arriving + /// load is discarded rather than painted, so no thread can reappear. + QString currentThreadId() const { return m_currentThreadId; } + /// The cid: namespace prefix for the nth message of a thread. /// /// MainWindow is the only producer of this value in the application. It @@ -95,6 +102,14 @@ private slots: void onThreadsReady(const QVector<ThreadSummary> &threads, quint64 generation); void onQueryFinished(int total, quint64 generation); void onThreadSelected(const QModelIndex ¤t, const QModelIndex &previous); + + /// Keeps the status bar's selection count and the multi-select guard in + /// step with selections that never move the current index. + void onSelectionChanged(); + + /// Pops up the thread-list context menu, preserving a multi-row selection + /// the click lands inside. + void showThreadContextMenu(const QPoint &pos); void onThreadLoaded(const QVector<MessageRef> &messages, quint64 generation); void onWorkerError(const QString &message); void onSyncFinished(bool success, int exitCode); @@ -196,6 +211,10 @@ private: QLineEdit *m_queryEdit = nullptr; QueryCompleter *m_queryCompleter = nullptr; QTableView *m_threadView = nullptr; + + /// Right-click menu for the thread list, holding the same QActions the + /// menu bar does. + QMenu *m_threadContextMenu = nullptr; QSplitter *m_splitter = nullptr; QComboBox *m_accountBox = nullptr; QPushButton *m_syncButton = nullptr; @@ -230,6 +249,10 @@ private: QString m_lastQuery; QString m_currentThreadId; + /// The selection count last written to the status bar, so it can be taken + /// back without clobbering a message some other action put there. + QString m_selectionMessage; + /// Confirmed tag mutations not yet known to have reached the mail store. /// /// A count of its own rather than QUndoStack::isClean(), which cannot serve |
